App intelligenti

Sblocca le funzionalità agentiche nella tua app con ML Kit GenAI

Altoparlanti

Informazioni

Informazioni sulla presentazione

Porta le funzionalità di AI di Android a un livello superiore creando agenti on-device autonomi. In questa sessione, approfondiremo le nuove API agentiche di ML Kit, create direttamente sulle API di accesso diretto ai modelli che già conosci.

Utilizzando l'app Jetpacker open source, imparerai a concatenare azioni in più passaggi su dati locali, gestire sessioni multi-turn stateful con contesti isolati e collegare Android MCP per potenziare sia gli assistenti in-app sia le piattaforme a livello di sistema Android con un'unica codebase.

Riepilogo

  • Gemini Nano 4 supporta in modo nativo la chiamata di funzione, consentendo ai modelli on-device di tradurre il linguaggio naturale in azioni e di gestire le API locali.
  • La libreria Jetpack AppFunctions definisce gli strumenti tramite descrizioni KDoc in linguaggio naturale. La modifica dei livelli di accesso può esporre queste funzioni ad agenti di sistema come Gemini.
  • La nuova libreria di flussi agentici (alpha) in ML Kit GenAI semplifica la creazione di agenti gestendo il loop di esecuzione e le chiamate agli strumenti.
  • AgentRunner coordina l'esecuzione degli strumenti e fornisce metodi di sessione per conversazioni multi-turno senza perdere il contesto.
  • L'accesso diretto tramite l'API Prompt rimane disponibile per un controllo semplice della cronologia o per configurazioni complesse come i sistemi multi-agente e ibridi.

Skill

Risorse

Gemini Notebook

Argomenti

  • ml-kit
  • on-device-ai
  • gemini-nano
  • ai-agents
  • chiamata di funzione
  • app-functions
  • aicore

Trascrizione

Scarica la trascrizione

Introduzione all'AI on-device e a Gemini Nano

  • Mi chiamo Jolanda Verhoef.

  • Sono un Developer Relations Engineer di Android e oggi sono in compagnia di Victoria Lestari, che fa parte del team di ingegneri di AICore.

  • Ciao.

  • Grazie per aver partecipato oggi.

  • Per questo motivo, il nostro team collabora da anni per portare i modelli di AI allo stato dell'arte fuori dal cloud ed eseguirli direttamente sui dispositivi Android.

  • Questi sforzi sono raggruppati nella suite ML Kit GenAI.

  • Da un lato, in questa suite ML Kit GenAI, abbiamo API di alto livello per soluzioni pronte all'uso, come riassunto di testo, descrizione di immagini e riconoscimento vocale.

  • D'altra parte, abbiamo anche aperto l'accesso diretto al modello on-device, Gemini Nano.

  • Oggi facciamo un ulteriore passo avanti.

  • Quindi, con questo accesso diretto, ti consentiremo di creare agenti in-app, agenti che possono ragionare su più turni, che possono salvare il contesto della conversazione e che possono intraprendere azioni nella tua app.

  • Che cosa significa in pratica, Victoria?

  • Sì.

  • Finora, quando parliamo di casi d'uso on-device, pensiamo ad attività più brevi, semplici e isolate, come il rilevamento di una probabile truffa o il riepilogo di una telefonata.

  • E quando vogliamo creare qualcosa di più complesso, come porre domande sul tuo catalogo di foto o chiedere a Google Maps, facciamo naturalmente riferimento ai modelli cloud di grandi dimensioni.

  • Quindi, i modelli cloud offrono il vantaggio di un'immensa elaborazione e di dati in tempo reale, ma l'AI on-device apre nuove possibilità per il trattamento dei dati locali, in tempo reale e con bassa latenza.

  • Quindi, Gemini Nano ha già ampliato ciò che puoi fare sui tuoi dispositivi.

  • Un esempio è Pixel Screenshots, che ti consente di cercare screenshot locali e fare domande in merito.

  • Quindi, quando eseguiamo gli agenti on-device, abbiamo bisogno di un grande salto di qualità nelle funzionalità del modello, ed è per questo che stiamo creando Gemini Nano 4, in grado di ragionare su più passaggi e intraprendere azioni reali.

  • Tuttavia, questa nuova funzionalità è importante solo se gli sviluppatori hanno dispositivi reali da scegliere come target.

  • La buona notizia è che Gemini Nano 4 è già disponibile su alcuni dispositivi di punta e puoi testarlo anche su molti altri dispositivi utilizzando l'Anteprima per gli sviluppatori di AICore.

  • Inoltre, siamo sulla buona strada per raggiungere i 100 milioni di dispositivi all'inizio del prossimo anno.

  • Ma che cosa significa?

  • Ciò significa che puoi iniziare a creare e testare queste esperienze oggi stesso, sapendo che ciò che stai creando oggi verrà esteso a un pubblico enorme.

  • Abbiamo parlato di Gemini Nano 4 e della copertura potenziale, quindi ora parliamo di cosa significano effettivamente le capacità agentiche.

  • In questo modo, l'AI agentica è in grado di pianificare attività complesse, suddividerle in più passaggi ed eseguirle nella tua app.

  • Quindi, supponiamo che il nostro modello Gemini Nano on-device possa eseguire query sul database locale, recuperare dati dal backend, ragionare e rispondere alle tue domande complesse, nonché attivare il workflow dell'app nativa sul tuo dispositivo.

  • Ma come fa un'AI a passare dalla pianificazione all'esecuzione di azioni reali?

  • Questo è l'argomento di oggi: la chiamata di funzione.

  • È il motore principale della capacità agentica.

  • Traduce il linguaggio naturale in esecuzione di sistema e guida il modello dai ragionamenti passivi all'esecuzione attiva selezionando e guidando le API locali necessarie per risolvere le attività complesse.

  • Siamo inoltre felici di annunciare che la chiamata di funzione è supportata in modo nativo su Gemini Nano 4.

  • Al momento è disponibile nell'Anteprima per gli sviluppatori di AICore e verrà presto implementato su tutti i dispositivi supportati da Nano 4.

  • È fantastico avere questo tipo di funzionalità nel modello, ma una funzionalità in un modello non fornisce ancora le API a te, in qualità di sviluppatore.

  • Per questo motivo, abbiamo lavorato per aggiungere le API giuste alla suite ML Kit GenAI per assicurarci che la creazione di un agente diventi un gioco da ragazzi.

  • Quindi, abbiamo iniziato con le API di accesso diretto che avevamo già e abbiamo aggiunto tipi di funzioni e tipi di cronologia delle conversazioni.

  • E con queste due nuove aggiunte all'API Prompt di ML Kit, ora potrai integrare queste capacità agentiche di basso livello nella tua app.

  • Tuttavia, a quel punto dovrai fare molte cose in autonomia.

  • Quindi, devi eseguire manualmente le funzioni, salvare la cronologia chiamate e ripetere più chiamate.

  • Quindi, se devi effettuare più chiamate, dovrai creare una sorta di sistema di loop e dovrai creare i tuoi guardrail per il sistema.

  • Questo è possibile, ma, ovviamente, vogliamo rendere tutto più semplice e veloce.

  • Quindi, oltre a questa libreria di API Prompt, abbiamo aggiunto anche una nuova libreria di flussi agentici.

  • Puoi trovarlo con com.mlkit.genai:agent-api, che è attualmente disponibile in versione alpha.

  • Ora, con questa nuova API, ci occupiamo noi di gran parte del lavoro pesante.

  • In questo modo, anziché dover eseguire molti passaggi manuali, potrai creare un agente con poche righe di codice.

  • Ed è esattamente quello che faremo oggi.

Demo dell'agente Jetpacker e chiamata di funzione

  • Vedremo quindi come funzionano effettivamente queste API con la nostra app di esempio Jetpacker.

  • Ora, hai già visto Jetpacker al Google I/O e durante questa conferenza.

  • Ma puoi scansionare questo codice QR e, se ti interessa, puoi dare un'occhiata al codice, puoi indirizzare il tuo agente al codice, puoi interagire con il codice e puoi scoprire di più su come abbiamo implementato tutte queste funzionalità.

  • Jetpacker è un moderno assistente di viaggio che abbiamo creato fondamentalmente per mostrare molte funzionalità intelligenti diverse.

  • Quindi, sta già utilizzando modelli on-device e modelli cloud per tutti i tipi di API Prompt, ma ora aggiungeremo un agente.

  • Per questo agente, Jetpacker, utilizzeremo la nuova API dell'agente GenAI di ML Kit di cui abbiamo appena parlato, che utilizzerà il sistema AI Core sottostante in esecuzione sui dispositivi Android per comunicare con il modello Gemini Nano.

  • Ora, posso parlare quanto voglio, e parleremo molto, ma, ovviamente, vogliamo mostrare tutto questo.

  • Passiamo allo smartphone e mostriamo una demo.

  • Ecco un Pixel 11 Pro.

  • E penso che possiamo passare a Pro, e dovresti essere in grado di vederlo.

  • Ecco, ci siamo.

  • Come puoi vedere, l'app Jetpacker è in esecuzione qui.

  • Contiene un elenco dei nostri prossimi viaggi e posso visualizzare quello attuale. Come puoi vedere, oggi è il 29 ottobre, quindi abbiamo il nostro workshop sull'AI on-device, la presentazione della demo e poi domani ho una crociera nella baia del Golden Gate e una visita al museo.

  • Tutto questo è fantastico.

  • Ora possiamo andare alla schermata delle spese, dove teniamo traccia del budget di questo viaggio.

  • Come potete vedere in basso a destra, c'è questa nuova piccola scintilla agentica, che è l'agente on-device che abbiamo creato.

  • Iniziamo con un comando piuttosto semplice.

  • Penso che possiamo chiedergli di aggiungere una spesa specifica.

  • Ad esempio, aggiungi una spesa per il taxi per Mountain View, pari a 45 $.

  • Ci siamo.

  • Sta preparando la spesa e l'ha aggiunta.

  • Incredibile.

  • Ok, la prima demo ha funzionato.

  • Torniamo alle slide e vediamo come ha funzionato.

  • Ok, prima di tutto, come avrai notato, stavo usando la mia voce, che in realtà utilizza anche il modello Gemini Nano tramite l'API Speech Recognition per trascrivere ciò che stavo dicendo in testo.

  • Il testo è perfetto e possiamo inviarlo al modello Gemini Nano.

  • Ma cosa farebbe?

  • Ad esempio, gli sto dicendo di aggiungere qualcosa alle spese, gli sto dicendo qualcosa su 45 $, ma non sa davvero come agire, giusto?

  • A questo punto, mi dirà semplicemente: "Non so di cosa stai parlando".

  • Quindi, vogliamo aggiungere un po' di informazioni al prompt.

  • Possiamo aggiungere strumenti al prompt, quindi possiamo aggiungere tutti i tipi di strumenti, in questo caso, aggiungo il budget del viaggio, l'itinerario e così via. Se lo passi al modello Nano con le nostre API, capirà automaticamente che, invece di restituire testo, ora è in grado di restituire una chiamata di funzione.

  • Quindi, nel nostro caso, dirà: "Beh, invece di rispondere con un testo, aggiungerò una spesa di 45 $per il taxi per Mountain View".

  • È fantastico, ma, Victoria, come funziona effettivamente?

  • Come si crea?

  • Sì.

  • Quindi, come definiamo gli strumenti nel nostro codice?

  • Questo è ciò che spiegherò.

  • Quindi, con Android MCP, utilizziamo la libreria Jetpack AppFunctions per definire e pubblicare questi strumenti.

  • Quindi, la libreria AppFunctions è in grado di esporre i servizi, i dati e le azioni della tua app nel registro del sistema operativo Android, in modo da poter riutilizzare le stesse funzioni per dichiarare e descrivere gli strumenti e utilizzarli nella tua app.

  • Inoltre, consente all'utente di completare questa attività tramite agenti e interazioni a livello di sistema.

  • Ti mostrerò un esempio concreto nel codice.

    /**
     * Adds an expense to a trip.
     *
     * @param tripId Identifier of the trip (e.g., "paris-2026").
     * @param amount Expense amount in dollars.
     * @param description Short description of what the expense was for.
     */
    @AppFunctionDeclaration(isDescribedByKDoc = true)
    @AppFunctionAccessLevel(
      level = ACCESS_LEVEL_SELF,
      isCompatEnforcementEnabled = true,
    )
    suspend fun addExpense(tripId: String, amount: Double, description: String) {
      // Save expense to local DB / repository...
    }
  • Come puoi vedere, questo è un esempio di AppFunctionDeclaration.

  • Come puoi vedere, il commento KDoc in alto descrive cosa fa la funzione e i suoi parametri, il tutto in linguaggio naturale.

  • Quindi, la best practice è fornire la descrizione più chiara possibile, perché la invieremo al modello.

  • Sotto puoi vedere un'annotazione, @AppFunctionAccessLevel.

  • In questo modo, l'ambito della visibilità della funzione viene limitato.

  • In questo esempio, abbiamo ACCESS_LEVEL_SELF, il che significa che queste funzioni che stiamo scrivendo sono visibili solo alla tua app, agli agenti della tua app.

  • Dopodiché, possiamo definire e implementare la funzione di sospensione stessa e scrivere tutte le funzioni dell'app che vogliamo, raggruppandole in un'unica classe di servizio.

Creazione e implementazione di un runner di agenti AI

  • Ok.

  • Quindi, abbiamo gli strumenti e possiamo scriverli, il che è fantastico.

  • Ma ovviamente, per creare un agente, abbiamo bisogno di qualcosa di più delle semplici AppFunctions.

  • Ci sono altre tre parti importanti da conoscere quando si scrive un agente.

  • Innanzitutto, definiamo l'agente.

  • Quindi definiamo le istruzioni di sistema, i guardrail, la configurazione, come lavorare con il sistema.

  • Abbiamo anche un AgentRunner.

  • AgentRunner è l'elemento che esegue l'agente per noi.

  • In questo modo, viene gestito il meccanismo di loop di cui parlavamo.

  • Infine, abbiamo AgentEvents, che è un modo per il sistema, per l'API, di farci sapere cosa sta effettivamente facendo.

  • Quindi, ci dirà quando restituisce testo, quando effettua una chiamata allo strumento e così via, in modo da poterlo visualizzare nella nostra UI.

  • Ok.

  • Diamo un'occhiata al codice effettivo.

  • Ok.

  • Prima di procedere con la scrittura del codice, dobbiamo verificare se la chiamata di funzione è disponibile sul tuo dispositivo, perché è supportata solo su Gemini Nano 4.

    val model = Generation.getClient()
    val features = model.getAvailableFeatures()
    if(FUNCTION_CALLING in features) {
      // Device is supported!
    } else {
      // Graceful fallback
    }
  • Innanzitutto, dobbiamo verificare chiamando il modello generativo getAvailableFeatures.

  • Possiamo semplicemente controllare se la chiamata di funzione è elencata tra le funzionalità.

  • In caso contrario, possiamo implementare un graceful fallback.

  • Ok.

  • Ora passiamo al codice.

    if(FUNCTION_CALLING in features) {
      // 1. Build the agent configuration
      val agent = Agent.builder()
        .setName("Assistant")
        .setInstructions("You are a helpful on-device assistant.")
        .enableAppFunctions()
        .build()
    
      // 2. Initialize the runner
      val runner = AgentRunner(agent = agent)
    
      // 3. Create your prompt
      val prompt = content { text(userPrompt)}
    
      // 4. Start the agent
      runner.runSingleTurnAsync(prompt).textTokens().collect { responseMessage += it }
    
    }
  • Sappiamo che la chiamata di funzione è supportata sul nostro dispositivo, quindi possiamo semplicemente scrivere start the a-- creare l'agente utilizzando Agent.builder.

  • Poi possiamo impostarne il nome.

  • Possiamo setInstructions per definire il contesto e la personalità.

  • Infine, chiamiamo enableAppFunctions.

  • Quindi, questa funzione eseguirà un loop e scoprirà automaticamente tutte le AppFunction che abbiamo elencato prima e abbiamo appena creato l'agente.

  • Successivamente, dobbiamo inizializzare AgentRunner.

  • Quindi, questo runner di agenti gestirà il loop di esecuzione per noi.

  • Lo chiamiamo semplicemente chiamando un costruttore e passandogli l'agente.

  • Infine, possiamo inserire il comando vocale trascritto in un blocco di contenuti per creare il prompt.

  • Poi chiamiamo il runner.

  • Chiamiamo questa funzione runSingleTurnAsync sul prompt, che avvierà l'agente, eseguirà l'inferenza e l'intera inferenza AI.

  • Se vogliamo solo trasmettere in streaming la risposta di testo finale del modello nella nostra UI, chiamiamo semplicemente textTokens, che raccoglierà la risposta man mano che arriva.

  • Questo è tutto.

  • Come vedi, si tratta di poche righe di codice, quindi non stavamo scherzando quando l'abbiamo detto.

  • Quindi, con poche righe di codice, hai un agente.

  • L'agente farà ciò che vuoi.

  • Ci darà una risposta testuale.

  • Ma ovviamente in questa prima demo che abbiamo mostrato, abbiamo eseguito solo un comando molto semplice, giusto?

  • Ad esempio, ho dato un comando e ha eseguito un'azione, tutto qui, quindi non è ancora un agente completo, giusto?

  • Quindi, rendiamo le cose un po' più interessanti.

  • Per la parte successiva, voglio dare un comando, ma voglio renderlo un po' più difficile per l'agente.

Architettura ed esecuzione degli agenti in più passaggi

  • Torniamo al mio dispositivo demo e vediamo se possiamo renderlo un po' più difficile.

  • Ci siamo già.

  • Incredibile.

  • Come ho detto, domani abbiamo in programma una visita al museo.

  • Per la visita al museo, se apro la pagina, puoi vedere che il prezzo di ingresso è di 25 $.

  • Ora vediamo se il nostro agente è effettivamente in grado di capirlo da solo.

  • Quindi, torno alla schermata delle spese e do un comando, dicendo: "Aggiungi il mio biglietto del museo alle mie spese".

  • Ci siamo.

  • E ora, come puoi vedere, sta controllando il mio itinerario, sta trovando la visita al museo e la sta aggiungendo alle mie spese.

  • In questo caso, sono necessari tre passaggi per raggiungere l'obiettivo e mi ha fornito anche una risposta di testo finale.

  • Anche in questo caso, tutto avviene automaticamente.

  • Hai visto la velocità reale di quel modello sul dispositivo.

  • Torniamo alle slide e vediamo come funziona.

  • Come puoi vedere, abbiamo apportato alcune modifiche rispetto alla nostra prima demo.

  • In questo caso, come puoi vedere, non ho fornito informazioni sul museo o sulla spesa, quindi, quando il modello ha ricevuto il prompt e gli strumenti, ha cercato di decidere cosa fare per prima cosa.

  • In questo caso, decide che deve saperne di più sul mio itinerario attuale, quindi chiamerà getItinerary.

  • Riceve una risposta e poi capisce che in questo itinerario è prevista una visita al museo, che probabilmente è quella di cui parlava.

  • Quindi, cerchiamo i dettagli di questo evento specifico.

  • Inoltre, tra tutti gli altri metadati di quell'evento specifico, vedrà che il valore è 25 $.

  • Capisce, torna indietro e capisce che deve chiamare l'aggiunta di una spesa.

  • Qui vediamo più passaggi che si susseguono uno dopo l'altro.

  • Victoria, puoi approfondire un po' questo aspetto?

  • Sì.

  • Quello che Jolanda ha appena descritto è un esempio concreto della demo che ha appena mostrato, ma qui lo porterò un po' più in un livello di astrazione più concettuale.

  • Questo accadrà ogni volta che eseguiamo una chiamata di funzione in più passaggi.

  • Innanzitutto, questa è la nostra app e questo è il modello Gemini Nano.

  • L'utente inizia inviando la query e l'elenco degli strumenti disponibili al modello.

  • Il modello utilizzerà quindi queste informazioni per decidere cosa inviare.

  • Supponiamo che, nel primo caso, restituisca una chiamata allo strumento, che chiameremo Chiamata allo strumento 1.

  • Poi, l'app eseguirà questa chiamata allo strumento e riceverà una risposta dello strumento.

  • Quindi, ciò che invierà al modello è in realtà la cronologia completa della conversazione, che include la query originale, l'elenco degli strumenti, la prima chiamata allo strumento e la prima risposta dello strumento.

  • Il modello, a sua volta, utilizza queste informazioni per decidere l'azione successiva.

  • Quindi, può continuare ad andare avanti e indietro fino a ottenere il risultato finale.

  • Supponiamo che il modello restituisca l'ennesima chiamata allo strumento e che l'app restituisca di nuovo l'intera cronologia delle conversazioni.

  • Quando il modello è soddisfatto, ovvero sa che la query è stata soddisfatta, invia una risposta finale all'app, che può mostrare nella UI che l'operazione è stata completata.

  • Ok.

  • Ora spiegherò come implementare questo concetto nel codice.

    /**
    * Retrieves the itinerary events (ID, title, location) for a trip.
    * Note: Call getEventDetails(eventId) to retrieve ticket prices or notes.
    */
    @AppFunctionDeclaration(isDescribedByKDoc = true)
    @AppFunctionAccessLevel(level = ACCESS_LEVEL_SELF, isCompatEnforcementEnabled = true)
    suspend fun getItinerary(tripId: String): List<EventSummary> { ... }
    
    /**
    * Retrieves details for a specific event, including admission ticket prices.
    */
    @AppFunctionDeclaration(isDescribedByKDoc = true)
    @AppFunctionAccessLevel(level = ACCESS_LEVEL_SELF, isCompatEnforcementEnabled = true)
    suspend fun getEventDetails(eventId: String): EventDetails { ... }
  • Ricorderai l'esempio del codice Single-Step. Questo è il codice Multi-Step, che sembra molto simile. Il punto più importante è scrivere il KDoc in modo molto, molto chiaro.

  • In questo esempio, la funzione getItinerary indica esplicitamente che restituisce solo un riepilogo di alto livello degli eventi.

  • Abbiamo anche detto che dobbiamo chiamare getEventDetails con un eventId se vogliamo avere dettagli più specifici sulla corsa.

  • Inoltre, getEventDetails ha anche il commento KDoc, che indica che può recuperare dettagli, come i prezzi dei biglietti e altre note.

  • Quindi, utilizzando il commento e descrivendolo chiaramente, possiamo guidare il modello a capire come gli strumenti interagiscono tra loro.

  • Ok.

  • Ecco come creiamo l'agente per eseguire tutto questo loop, questo loop agentico.

    val agent = Agent.builder()
     .setName("Assistant")
     .setInstructions("You are a helpful travel assistant. Active tripId: $tripId")
     .enableAppFunctions()
     .setMaxTurns(5) // Guardrail against infinite tool loops
     .build()
    val runner = AgentRunner(agent = agent)
    // Automatically loops through all 3 tool calls in a single user turn!
    runner.runSingleTurnAsync(prompt).collect { event ->
     when (event) {
       is AgentEvent.FunctionCall -> showToolRunning(event.name)
       is AgentEvent.FunctionResponse -> showToolCompleted(event.name)
       is AgentEvent.TextMessage -> responseMessage += event.text
     }
    }
  • È molto simile all'esempio di un singolo passaggio, con solo due aggiunte.

  • Il primo è che dobbiamo impostare il numero massimo di turni e, in questo esempio, ne impostiamo solo cinque.

  • Come funziona?

  • In questo modo, il modello non entra in un loop infinito.

  • Supponiamo che abbia un prompt ambiguo o che continui a chiamare uno strumento dopo l'altro e poi di nuovo indietro, quindi, se impostiamo questo valore, si fermerà, non continuerà a ripetere il loop.

  • Poi, chiamiamo sempre la stessa funzione per eseguirla, runSingleTurnAsync.

  • Quindi, anche se si chiama single-turn, in realtà un turno significa una query dell'utente.

  • Quindi, l'utente finale si aspetta che io invii una query e tu restituisca una risposta, senza preoccuparsi troppo dei dettagli, come cosa è successo dietro le quinte, quante volte è stato eseguito il loop e così via.

  • Quindi, possiamo continuare a utilizzare la stessa funzione.

  • Inoltre, poiché la chiamata allo strumento in più passaggi richiede un po' più di tempo, nella demo hai visto che mostra anche quale funzione sta chiamando in quel momento, cosa sta eseguendo in quel momento e se ha finito.

  • Quindi, vogliamo mostrare l'avanzamento in tempo reale nella UI.

  • Quindi, invece di raccogliere solo la risposta di testo, possiamo effettivamente visualizzare il nome dello strumento, event.name, ogni volta che AgentEvent emette un FunctionCall o un FunctionResponse e, al termine, possiamo semplicemente restituire TextMessage come prima.

Gestione delle sessioni di conversazione a più turni

  • Formidabile!

  • Quindi, chiamiamo questa esecuzione asincrona single-turn, il che, ovviamente, presuppone che esista anche un altro tipo di API, giusto?

  • Sì.

  • Ovviamente, non è sufficiente eseguire un solo passaggio.

  • Vogliamo eseguire più passaggi.

  • Passiamo ora alle sessioni multi-turn e penso che possiamo tornare alla mia demo e renderla ancora un po' più difficile.

  • Stavamo parlando di come abbiamo una visita al museo, ma anche di una crociera nella baia del Golden Gate.

  • Quindi, per questo, voglio solo capire se ho un budget sufficiente prima di prenotarlo.

  • Quindi, facciamo una domanda.

  • Vediamo.

  • Sto pensando di portare il mio team a fare la crociera nella baia del Golden Gate domani e pensavo che costasse circa 180 $.

  • Se lo faccio, quale sarà il mio budget rimanente?

  • Cosa sta facendo?

  • Risponde alla domanda su quale sarà il mio budget rimanente.

  • In questo modo, comprende l'itinerario attuale.

  • Dispone di queste informazioni e può comunicarle.

  • Ma posso anche fare una domanda aggiuntiva qui.

  • Ok, bene.

  • Puoi aggiungerlo alle mie spese?

  • Ecco fatto, ora andiamo avanti.

  • Ah, serve la valuta, quindi, fantastico, effetto demo.

  • Ok, quindi è in dollari.

  • 180 $.

  • Ci siamo.

  • È la prima volta che succede, è fantastico.

  • Ok, cosa sta facendo?

  • Mi dice che ci sono voluti tre turni e che sono stati aggiunti alle spese. Inoltre, mi consente di aggiornare questa sessione.

  • In alto c'è un pulsante di aggiornamento che mi permette di cancellare l'intera cronologia e ricominciare da capo.

  • A questo punto, penso che possiamo tornare alle nostre slide.

  • Abbiamo le demo funzionanti, fanno tutte ciò che vogliamo, quindi penso che possiamo tornare al codice.

  • Sì.

  • È davvero fantastico.

  • Come vedi, Jolanda non ha menzionato, dice solo che è in dollari, non dice che è per il biglietto della Golden Gate Bay Cruise.

  • Come fa il modello a conoscere tutto il contesto?

  • Questo è un po' diverso da quello che ti ho mostrato nei due snippet di codice precedenti.

    // 1. Get or create an isolated session for the current trip
    val session = runner.getOrCreateSession(tripId)
    
    // 2. Run a turn (history is preserved automatically across turns)
    session.runAsync(prompt).collect { event ->
      when (event) {
        is AgentEvent.FunctionCall -> showToolRunning(event)
        is AgentEvent.FunctionResponse -> showToolCompleted(event)
        is AgentEvent.TextMessage -> responseMessage += event.text
      }
    }
    
    // 3. Clear history when the user taps "New conversation"
    session.delete()
  • Quindi, invece di utilizzare AgentRunner e runSingleTurnAsync, dobbiamo creare una sessione.

  • Qui chiamiamo runner.getOrCreateSession con tripId.

  • Perché dobbiamo creare una sessione?

  • Perché runSingleTurnAsync reimposta la memoria, quindi non avrà contesto.

  • Non potremo avere una conversazione multi-turno.

  • Quindi, quando vuoi avere una conversazione a più turni, utilizziamo la sessione, la sessione dell'agente.

  • Quindi trasmettiamo il tripId.

  • Perché dobbiamo farlo?

  • Come hai già visto, l'app Jetpacker gestisce più viaggi.

  • C'è un viaggio ADS, un viaggio a Parigi, forse un viaggio a Tokyo.

  • Quindi vogliamo mantenere il contesto all'interno del viaggio.

  • Non vogliamo metterci i bastoni tra le ruote, sai?

  • Quindi, invece di chiamare runSingleTurnAsync, abbiamo runAsync.

  • Quindi questo è diverso.

  • Questo viene eseguito nella sessione e poi abbiamo ancora lo stesso prompt.

  • Poi possiamo ancora emettere AgentEvent, FunctionCall, FunctionResponse e TextMessage come prima, è lo stesso.

  • E poi, quando Jolanda ha fatto clic sul pulsante per una nuova conversazione, il pulsante di aggiornamento, possiamo effettivamente avere session.delete.

  • Sì.

  • Ok.

  • Abbiamo fatto tutto questo con le nuove API agentiche, il che è fantastico e funzionerà alla grande per molti dei vostri casi d'uso.

  • Ma, naturalmente, vogliamo sempre rendere possibili le cose veloci o facili, ma vogliamo anche rendere possibili i casi più complessi.

  • Quindi vogliamo assicurarci che, se necessario, tu possa comunque utilizzare le API di accesso diretto per creare una versione di livello più basso di queste cose.

  • Quindi, Victoria, puoi spiegare cosa significa e quando dovremmo tornare a questo livello di accesso diretto?

Agentic Workflows avanzati e integrazione degli strumenti

  • Sì, certo, Jolanda.

  • Questo accesso diretto potrebbe già esserti familiare con l'API Prompt.

  • Quindi, abbiamo supportato anche la chiamata di funzione qui e abbiamo due casi d'uso in cui preferiremmo utilizzare questa API anziché il flusso agentico più semplice.

  • Il primo è un caso d'uso molto semplice.

  • Come ricorderai, nel flusso agentico inviamo l'intera conversazione, la query, gli strumenti, la chiamata allo strumento, la risposta dello strumento, tutto al modello.

  • E forse ci sono alcuni casi d'uso in cui non vogliamo farlo, vogliamo solo inviare il prompt, ad esempio.

  • Supponiamo di voler inviare un prompt al modello che restituisce una chiamata allo strumento e di non voler restituire la chiamata allo strumento, ma di voler solo inviare un altro prompt al modello, in modo da poter modificare la cronologia, come se avessimo il controllo completo sulla cronologia.

  • Ecco un esempio più concreto.

    // Turn 1: Execute function directly without a second model call
    val turn1 = content(Content.Role.USER) {
      text("Change the background to a sunset")
    }
    val response1 = generativeModel.generateContent(
      generateContentRequest(turn1) { setTools(tools) }
    )
    executeFunctionCalls(response1)
    
    // Turn 2: Send only user prompts to save context tokens
    val customHistory = listOf(
      turn1,
      content(Content.Role.USER) { text("Oh no, I meant a beach") }
    )
    val response2 = generativeModel.generateContent(
      GenerateContentRequest.Builder(customHistory).setTools(tools).build()
    )
  • Supponiamo di avere un'app che può cambiare il tema dello sfondo del tuo dispositivo.

  • Supponiamo che l'utente invii questa query: "Cambia lo sfondo con un tramonto".

  • E poi, sì, la nostra app ha eseguito questa funzione, lo sfondo è cambiato in un tramonto.

  • Poi l'utente ha cambiato idea e ha detto: "Oh, no, intendevo una spiaggia".

  • In questo caso, non è necessario inviare lo stato completo che indica che il dispositivo ha già cambiato lo sfondo in un tramonto e che ora vuole cambiarlo in una spiaggia.

  • No, non è necessario.

  • Dobbiamo solo inviare il primo e il secondo prompt, perché il primo contiene le informazioni di uno sfondo.

  • In questo modo, capisce che l'utente vuole cambiare lo sfondo con una spiaggia.

  • Per farlo, possiamo utilizzare l'API Prompt.

  • Abbiamo il pieno controllo sulla cronologia, salviamo il token di contesto on-device perché, come sai, on-device ha una finestra di token più limitata e in questo modo possiamo salvare più token.

  • Il secondo caso d'uso è l'estremo opposto.

  • Quindi, se abbiamo casi d'uso complessi che l'API agentica non può ancora supportare, ad esempio se abbiamo sistemi multi-agente in cui diversi sub-agenti interagiscono tra loro per risolvere un'attività complessa, o un sistema ibrido in cui abbiamo sia modelli on-device, ma vogliamo anche avere modelli cloud, o se vogliamo avere una memoria personalizzata e una compattazione del contesto.

  • Questo si riferisce alla cronologia, come il controllo completo della cronologia che ho menzionato prima.

  • Quindi, supponiamo che, poiché la finestra di contesto è limitata, non vogliamo inviare tutto al modello, ma vogliamo solo riassumere la parte più importante o forse l'ultima parte, in modo da avere il pieno controllo su questo, quindi utilizziamo l'API di accesso diretto.

  • Per questi casi d'uso, possiamo utilizzare framework come Agent Development Kit (ADK) o Crew, oppure puoi anche creare il tuo framework di orchestrazione personalizzato.

  • Ok.

  • Questo è tutto ciò che avevamo sull'API agentica.

  • Tuttavia, c'è un'altra cosa che vorremmo toccare.

  • Abbiamo parlato di agenti in-app, quindi stai creando la tua esperienza con gli agenti all'interno della tua app, ma abbiamo anche parlato di come utilizziamo AppFunctions e perché dovresti utilizzare AppFunctions se l'unica cosa che fai è all'interno della tua app?

  • Beh, sappiamo che agli sviluppatori come te non piace fare le cose due volte, giusto?

  • Non ci piace copiare e incollare il codice, non ci piace dover duplicare la logica di business.

  • Quindi, con AppFunctions, invece di consentire solo al nostro agente in-app di utilizzarle, con una modifica molto semplice, possiamo rendere i nostri strumenti disponibili anche agli agenti di sistema.

  • Come si presenta il codice?

  • Sì.

  • Con AppFunctions, dobbiamo solo scrivere una volta e poi possiamo esporre le funzioni a diversi livelli di strati, come vogliamo.

  • Ricordi che prima avevamo ACCESS_LEVEL_SELF, che espone le funzioni che abbiamo scritto solo al nostro agente in-app, ma possiamo anche modificare questo livello di accesso in ACCESS_LEVEL_ANDROID_TRUSTED.

  • In questo modo, le funzioni verranno esposte ad agenti attendibili nel sistema operativo Android.

  • Non è quindi necessario riscrivere le funzioni, i comandi o la logica del database.

  • AppFunctions funge già da voce della tua app sia internamente che esternamente.

  • Vogliamo una demo anche di questo, giusto?

  • Ok, torniamo allo smartphone e, per la nostra demo finale, usciamo dall'app Jetpacker.

  • Quindi possiamo uscire e andare su Gemini.

  • Quindi, in Gemini, possiamo chiamare @Jetpacker.

  • Tieni presente che questa funzionalità è disponibile solo in versione beta limitata, quindi non sarà disponibile per tutti oggi, ma è possibile testarla e provarla.

  • Vediamo Jetpacker.

  • Cosa dobbiamo fare?

  • Oh, ho bevuto un caffè stamattina, giusto?

  • Puoi aggiungere un caffè da 7 $alle spese di viaggio ADS, per favore?

  • Ci siamo.

  • Come puoi vedere, si connette all'app Jetpacker.

  • Ecco fatto.

  • Ha aggiunto la spesa per me.

  • In pratica, abbiamo visto come utilizzare tutto in Jetpacker.

  • La cosa bella di Jetpacker è che possiamo anche attivare la modalità aereo.

  • Oggi non l'abbiamo mostrato, ma possiamo semplicemente attivare la modalità aereo e tutto ciò che avete visto nelle demo, e voglio mostrarlo perché voglio assicurarmi che tutti voi vi fidiate di me, quindi disattiviamo il Wi-Fi.

  • Qualcuno ha un buon esempio di demo, una spesa che posso aggiungere?

  • Vediamo.

  • Forse possiamo fare "Che cos'è?"

  • Oh, il noleggio auto sarebbe bello, vero?

  • Ok.

  • Proviamo con "Aggiungi una spesa di 300 $per il noleggio auto".

  • Ecco fatto, ha capito.

  • Ok.

  • Incredibile.

  • Ok, torniamo alle slide.

  • Stavamo parlando di AppFunctions.

  • So che ne abbiamo parlato molto, molto brevemente.

  • Se vuoi saperne di più, c'è anche un altro intervento di Dan e Sarah che approfondisce molto di più come puoi effettivamente abilitare questa funzionalità, come puoi testarla, quali strumenti abbiamo e così via.

  • Oggi abbiamo affrontato molti argomenti diversi.

  • Abbiamo visto i passaggi singoli, i passaggi multipli, i turni multipli e, infine, abbiamo anche visto come utilizzare l'AppFunction di sistema.

  • Ora, prima di lasciarci, vorrei darti un paio di suggerimenti.

Best practice e conclusione

  • Sì.

  • Quindi, ricorda questi tre suggerimenti quando iniziamo a creare queste esperienze basate su agenti.

  • Il primo è mantenere l'attenzione sui set di strumenti.

  • Ricorda che i modelli on-device hanno funzionalità limitate a causa delle loro dimensioni ridotte.

  • Quindi, invece di registrare decine di strumenti, è meglio sceglierne un numero inferiore.

  • E poi, come ho detto prima, scrivi descrizioni KDoc chiare perché è ciò che il modello utilizza per decidere cosa restituire.

  • Inoltre, dobbiamo impostare un guardrail per il numero massimo di turni.

  • In questo modo, si evita anche che il modello entri in un loop infinito se si confonde.

  • Ciò significa che potremmo consumare la batteria dell'utente.

  • In questo modo, possiamo risparmiare la batteria dell'utente.

  • Infine, dobbiamo verificare la disponibilità se la chiamata di funzione è già supportata sul dispositivo e implementare un fallback controllato.

  • Ad esempio, se non è supportata, puoi fare riferimento ai modelli cloud o alla tua UI standard.

  • Ok, credo che possiamo concludere con le nostre risorse.

  • Ancora una volta, ti mostrerò i codici QR.

  • Dai un'occhiata.

  • Penso che una delle cose da tenere a mente sia che abbiamo anche Android Skills per questo.

  • In questo modo, non devi approfondire ogni singolo dettaglio.

  • Abbiamo una documentazione completa e una skill Android da abbinare a questa nuova API agentica.

  • Dai un'occhiata.

  • Scarica l'Anteprima per gli sviluppatori AICore per testare tutte queste funzionalità e buon lavoro con la programmazione.

Codice

Snippet di codice

Snippet Kotlin alle 09:53 in "Jetpacker agent demo and chiamata di funzione"
/**
 * Adds an expense to a trip.
 *
 * @param tripId Identifier of the trip (e.g., "paris-2026").
 * @param amount Expense amount in dollars.
 * @param description Short description of what the expense was for.
 */
@AppFunctionDeclaration(isDescribedByKDoc = true)
@AppFunctionAccessLevel(
  level = ACCESS_LEVEL_SELF,
  isCompatEnforcementEnabled = true,
)
suspend fun addExpense(tripId: String, amount: Double, description: String) {
  // Save expense to local DB / repository...
}
Snippet Kotlin al minuto 11:41 in "Creazione e implementazione di un runner di agenti AI"
val model = Generation.getClient()
val features = model.getAvailableFeatures()
if(FUNCTION_CALLING in features) {
  // Device is supported!
} else {
  // Graceful fallback
}
Snippet Kotlin alle 12:07 in "Creazione e implementazione di un runner di agenti AI"
if(FUNCTION_CALLING in features) {
  // 1. Build the agent configuration
  val agent = Agent.builder()
    .setName("Assistant")
    .setInstructions("You are a helpful on-device assistant.")
    .enableAppFunctions()
    .build()

  // 2. Initialize the runner
  val runner = AgentRunner(agent = agent)

  // 3. Create your prompt
  val prompt = content { text(userPrompt)}

  // 4. Start the agent
  runner.runSingleTurnAsync(prompt).textTokens().collect { responseMessage += it }

}
Snippet Kotlin alle 17:23 in "Architettura ed esecuzione degli agenti in più passaggi"
/**
* Retrieves the itinerary events (ID, title, location) for a trip.
* Note: Call getEventDetails(eventId) to retrieve ticket prices or notes.
*/
@AppFunctionDeclaration(isDescribedByKDoc = true)
@AppFunctionAccessLevel(level = ACCESS_LEVEL_SELF, isCompatEnforcementEnabled = true)
suspend fun getItinerary(tripId: String): List<EventSummary> { ... }

/**
* Retrieves details for a specific event, including admission ticket prices.
*/
@AppFunctionDeclaration(isDescribedByKDoc = true)
@AppFunctionAccessLevel(level = ACCESS_LEVEL_SELF, isCompatEnforcementEnabled = true)
suspend fun getEventDetails(eventId: String): EventDetails { ... }
Snippet Kotlin al minuto 18:23 in "Architettura ed esecuzione di agenti in più passaggi"
val agent = Agent.builder()
 .setName("Assistant")
 .setInstructions("You are a helpful travel assistant. Active tripId: $tripId")
 .enableAppFunctions()
 .setMaxTurns(5) // Guardrail against infinite tool loops
 .build()
val runner = AgentRunner(agent = agent)
// Automatically loops through all 3 tool calls in a single user turn!
runner.runSingleTurnAsync(prompt).collect { event ->
 when (event) {
   is AgentEvent.FunctionCall -> showToolRunning(event.name)
   is AgentEvent.FunctionResponse -> showToolCompleted(event.name)
   is AgentEvent.TextMessage -> responseMessage += event.text
 }
}
Snippet Kotlin al minuto 22:13 in "Gestione delle sessioni di conversazione multi-turn"
// 1. Get or create an isolated session for the current trip
val session = runner.getOrCreateSession(tripId)

// 2. Run a turn (history is preserved automatically across turns)
session.runAsync(prompt).collect { event ->
  when (event) {
    is AgentEvent.FunctionCall -> showToolRunning(event)
    is AgentEvent.FunctionResponse -> showToolCompleted(event)
    is AgentEvent.TextMessage -> responseMessage += event.text
  }
}

// 3. Clear history when the user taps "New conversation"
session.delete()
Snippet di Kotlin al minuto 25:02 in "Advanced agentic workflows and tool integration"
// Turn 1: Execute function directly without a second model call
val turn1 = content(Content.Role.USER) {
  text("Change the background to a sunset")
}
val response1 = generativeModel.generateContent(
  generateContentRequest(turn1) { setTools(tools) }
)
executeFunctionCalls(response1)

// Turn 2: Send only user prompts to save context tokens
val customHistory = listOf(
  turn1,
  content(Content.Role.USER) { text("Oh no, I meant a beach") }
)
val response2 = generativeModel.generateContent(
  GenerateContentRequest.Builder(customHistory).setTools(tools).build()
)