Applications intelligentes

Exploitez les capacités agentiques de votre application avec ML Kit GenAI

Haut-parleurs

À propos

À propos de l'intervention

Faites passer vos fonctionnalités d'IA Android au niveau supérieur en créant des agents autonomes sur l'appareil. Dans cette session, nous allons explorer les nouvelles API agentiques de ML Kit, conçues directement sur les API d'accès direct aux modèles que vous connaissez déjà.

À l'aide de l'application Open Source Jetpacker, vous apprendrez à enchaîner des actions en plusieurs étapes sur des données locales, à gérer des sessions multitours avec état avec des contextes isolés et à connecter Android MCP pour alimenter à la fois les assistants intégrés à l'application et les surfaces au niveau du système Android avec une seule codebase.

Résumé

  • Gemini Nano 4 est nativement compatible avec l'appel de fonction, ce qui permet aux modèles sur l'appareil de traduire le langage naturel en actions et de piloter des API locales.
  • La bibliothèque Jetpack AppFunctions définit les outils via des descriptions KDoc en langage naturel. La modification des niveaux d'accès peut exposer ces fonctions à des agents système tels que Gemini.
  • La nouvelle bibliothèque de flux agentiques (alpha) de ML Kit GenAI simplifie la création d'agents en gérant la boucle d'exécution et les appels d'outils.
  • AgentRunner coordonne l'exécution des outils et fournit des méthodes de session pour les conversations multitours sans perdre le contexte.
  • L'accès direct via l'API Prompt reste disponible pour un contrôle simple de l'historique ou des configurations complexes telles que les systèmes multi-agents et hybrides.

Compétences

Ressources

Gemini Notebook

Thèmes

  • ml-kit
  • on-device-ai
  • gemini-nano
  • ai-agents
  • appel de fonction
  • app-functions
  • aicore

Transcription

Présentation de l'IA sur l'appareil et de Gemini Nano

  • Je m'appelle Jolanda Verhoef.

  • Je suis ingénieur DevRel pour Android, et je suis accompagné aujourd'hui de Victoria Lestari, qui fait partie de l'équipe d'ingénierie AICore.

  • Bonjour,

  • Merci de nous avoir rejoints ce matin.

  • C'est pourquoi notre équipe collabore étroitement depuis des années pour extraire des modèles d'IA de pointe du cloud et les exécuter directement sur les appareils Android.

  • Ces efforts sont regroupés dans la suite ML Kit GenAI.

  • D'une part, dans cette suite d'IA générative ML Kit, nous disposons d'API de haut niveau pour des solutions clés en main, comme la synthèse de texte, la description d'images et la reconnaissance vocale.

  • D'autre part, nous avons également ouvert l'accès direct au modèle sur l'appareil, Gemini Nano.

  • Aujourd'hui, nous allons encore plus loin.

  • Grâce à cet accès direct, nous allons vous permettre de créer des agents intégrés à l'application, capables de raisonner sur plusieurs tours, d'enregistrer le contexte de la conversation et d'effectuer des actions dans votre application.

  • Qu'entendons-nous par là, Victoria ?

  • Oui.

  • Jusqu'à présent, lorsque nous parlons de cas d'utilisation sur l'appareil, nous pensons à des tâches plus courtes, plus simples et isolées, comme la détection d'une escroquerie probable ou la synthèse d'un appel téléphonique.

  • Et lorsque nous voulons créer quelque chose de plus complexe, comme poser des questions sur votre catalogue de photos ou interroger Google Maps, nous nous tournons naturellement vers de grands modèles cloud.

  • Les modèles cloud offrent donc l'avantage d'un traitement immense et de données en temps réel, mais l'IA sur l'appareil ouvre de nouvelles possibilités pour une faible latence et un traitement des données en temps réel et local.

  • Gemini Nano a donc déjà élargi les possibilités de vos appareils.

  • C'est le cas de Pixel Screenshots, qui vous permet de rechercher des captures d'écran locales et de poser des questions à leur sujet.

  • Pour exécuter des agents sur l'appareil, nous devons donc améliorer considérablement les capacités du modèle. C'est pourquoi nous développons Gemini Nano 4, qui est capable de raisonner sur plusieurs étapes et d'effectuer des actions réelles.

  • Mais cette nouvelle fonctionnalité n'est utile que si les développeurs disposent d'appareils réels à cibler.

  • La bonne nouvelle, c'est que Gemini Nano 4 est déjà disponible sur certains appareils phares, et que vous pouvez également le tester sur de nombreux autres appareils à l'aide de la preview développeur d'AICore.

  • Nous sommes également en bonne voie pour atteindre 100 millions d'appareils au début de l'année prochaine.

  • Qu'est-ce que cela signifie ?

  • Cela signifie que vous pouvez commencer à créer et à tester ces expériences dès aujourd'hui, en sachant que ce que vous créez aujourd'hui sera accessible à une audience massive.

  • Nous avons parlé de Gemini Nano 4 et de sa portée potentielle. Voyons maintenant ce que signifient réellement les capacités agentiques.

  • L'IA agentique est donc capable de planifier des tâches complexes, de les décomposer en plusieurs étapes, puis de les exécuter dans votre application.

  • Imaginons que notre modèle Gemini Nano sur l'appareil puisse interroger votre base de données locale, récupérer des données du backend, raisonner et répondre à vos questions complexes, et déclencher un workflow d'application native sur votre appareil.

  • Mais comment une IA passe-t-elle concrètement de la planification à l'action ?

  • C'est le sujet du jour : l'appel de fonction.

  • C'est le moteur principal de la capacité agentique.

  • Il traduit le langage naturel en exécution système et fait passer le modèle d'un raisonnement passif à une exécution active en sélectionnant et en pilotant les API locales nécessaires pour résoudre les tâches complexes.

  • Nous sommes également heureux d'annoncer que l'appel de fonction est désormais compatible de manière native avec Gemini Nano 4.

  • Il est actuellement disponible dans la Preview développeur AICore et sera bientôt déployé sur tous les appareils compatibles avec Nano 4.

  • C'est formidable que le modèle dispose de ce type de fonctionnalités, mais une fonctionnalité dans un modèle ne vous fournit pas encore les API en tant que développeur.

  • Nous avons donc également travaillé à l'ajout des API appropriées à la suite ML Kit GenAI pour que la création d'un agent devienne un jeu d'enfant.

  • Nous avons donc commencé par les API d'accès direct dont nous disposions déjà, et nous y avons ajouté des types de fonctions et d'historique de conversation.

  • Grâce à ces deux nouveaux ajouts à l'API Prompt de ML Kit, vous pourrez désormais intégrer ces capacités agentiques de bas niveau à votre application.

  • Cependant, vous devez effectuer vous-même un certain nombre d'opérations à ce stade.

  • Vous devez donc exécuter manuellement les fonctions, enregistrer l'historique des appels et effectuer vous-même une boucle sur plusieurs appels.

  • Si vous devez effectuer plusieurs appels, vous devrez créer vous-même un système de boucle et définir vos propres garde-fous pour le système.

  • C'est possible, mais nous voulons bien sûr que ce soit rapide et facile.

  • En plus de cette bibliothèque d'API Prompt, nous avons également ajouté une nouvelle bibliothèque de flux agentiques.

  • Vous pouvez le trouver avec com.mlkit.genai:agent-api, et il est actuellement disponible en version alpha.

  • Désormais, avec cette nouvelle API, nous faisons le gros du travail à votre place.

  • Ainsi, au lieu d'effectuer de nombreuses étapes manuelles, vous pourrez créer un agent avec seulement quelques lignes de code.

  • C'est exactement ce que nous allons faire aujourd'hui.

Démo de l'agent Jetpacker et appel de fonction

  • Nous allons donc voir comment ces API fonctionnent avec notre application exemple Jetpacker.

  • Vous avez déjà vu Jetpacker à Google I/O et pendant cette conférence.

  • Mais vous pouvez scanner ce QR code. Si vous le souhaitez, vous pouvez examiner le code, pointer votre agent vers le code, interagir avec le code et en savoir plus sur la façon dont nous avons implémenté toutes ces fonctionnalités.

  • Jetpacker est un assistant de voyage moderne que nous avons conçu pour présenter de nombreuses fonctionnalités intelligentes.

  • Il utilise donc déjà des modèles sur l'appareil et des modèles cloud pour toutes sortes d'API Prompt, mais nous allons maintenant y ajouter un agent.

  • Pour cet agent, Jetpacker, nous allons utiliser la nouvelle API d'agent d'IA générative ML Kit dont nous venons de parler. Elle utilisera le système AI Core sous-jacent qui s'exécute sur les appareils Android pour communiquer avec le modèle Gemini Nano.

  • Je pourrais continuer à parler pendant des heures, et nous allons d'ailleurs beaucoup discuter, mais nous voulons bien sûr vous montrer tout cela.

  • Passons au téléphone et faisons une démonstration.

  • J'ai un Pixel 11 Pro ici.

  • Je pense que nous pouvons passer au Pro, et vous devriez pouvoir le voir.

  • Voilà, c'est fait.

  • Comme vous pouvez le voir, l'application Jetpacker est en cours d'exécution.

  • Il contient plusieurs de nos voyages à venir. Je peux consulter notre voyage actuel, et dans notre voyage actuel, vous pouvez voir que nous sommes actuellement le 29 octobre. Nous avons donc notre atelier sur l'IA sur l'appareil, une démonstration, puis demain, j'ai une croisière dans la baie du Golden Gate et une visite de musée à venir.

  • Tout cela est formidable.

  • Nous pouvons maintenant accéder à l'écran des dépenses, où nous suivons le budget de ce voyage.

  • Comme vous pouvez le voir en bas à droite, il y a une nouvelle petite étincelle d'agentivité, qui représente l'agent sur l'appareil que nous avons créé.

  • Commençons par une commande assez simple.

  • Je pense qu'on peut lui demander d'ajouter une dépense spécifique.

  • Par exemple, "Ajoute une dépense de 45 dollars pour le taxi jusqu'à Mountain View".

  • Bien.

  • La dépense est en cours de préparation, puis elle est ajoutée.

  • Parfait.

  • La première démo a fonctionné.

  • Revenons aux diapositives et voyons comment cela a fonctionné.

  • Tout d'abord, comme vous l'avez remarqué, j'ai utilisé ma voix. En fait, cette voix utilise également le modèle Gemini Nano, qui utilise l'API de reconnaissance vocale pour transcrire ce que je disais en texte.

  • Ce texte est parfait, et nous pouvons l'envoyer au modèle Gemini Nano.

  • Mais que ferait-il ?

  • Par exemple, je lui demande d'ajouter quelque chose aux dépenses, je lui parle de 45 $, mais il ne sait pas vraiment quoi faire, n'est-ce pas ?

  • À ce stade, il me dira simplement : "Je ne sais pas de quoi vous parlez."

  • Nous allons donc ajouter quelques éléments à ce prompt.

  • Nous pouvons ajouter des outils au prompt, de toutes sortes. Dans ce cas, j'ajoute l'obtention du budget du voyage, de l'itinéraire, etc. Et si vous transmettez cela au modèle Nano avec nos API, il comprendra automatiquement qu'au lieu de renvoyer du texte, il peut désormais renvoyer un appel de fonction.

  • Dans notre cas, il va donc dire : "Au lieu de répondre par du texte, je vais ajouter une dépense de 45 $ pour le taxi vers Mountain View."

  • C'est formidable, mais, Victoria, comment cela fonctionne-t-il concrètement ?

  • Comment créer ce flux ?

  • Oui.

  • Alors, comment définir les outils dans notre code ?

  • C'est ce que je vais vous expliquer.

  • Avec Android MCP, nous utilisons donc la bibliothèque Jetpack AppFunctions pour définir et publier ces outils.

  • La bibliothèque AppFunctions peut donc exposer les services, les données et les actions de votre application dans le registre de l'OS Android, ce qui nous permet de réutiliser exactement les mêmes fonctions pour déclarer et décrire les outils et les utiliser dans votre application.

  • Cela permet également à l'utilisateur d'effectuer cette tâche par le biais d'agents et d'interactions au niveau du système.

  • Je vais vous donner un exemple concret dans le code.

    /**
     * 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...
    }
  • Vous pouvez voir qu'il s'agit d'un exemple de AppFunctionDeclaration.

  • Comme vous pouvez le voir, le commentaire KDoc en haut décrit ce que fait la fonction et ses paramètres, le tout en langage naturel.

  • Il est donc recommandé de fournir la description la plus claire possible, car nous allons l'envoyer au modèle.

  • En dessous, vous pouvez voir une annotation, @AppFunctionAccessLevel.

  • Cela permet de définir la visibilité de la fonction.

  • Dans cet exemple, nous avons ACCESS_LEVEL_SELF, ce qui signifie que les fonctions que nous écrivons ne sont visibles que par votre application et ses agents.

  • Ensuite, nous pouvons définir et implémenter la fonction de suspension elle-même, et nous pouvons écrire autant de fonctions d'application que possible et les regrouper toutes dans une seule classe de service.

Créer et implémenter un exécuteur d'agent IA

  • OK.

  • Nous disposons donc des outils nécessaires et nous pouvons les écrire, ce qui est formidable.

  • Mais bien sûr, pour créer un agent, nous avons besoin d'un peu plus que ces AppFunctions.

  • Il existe trois autres types d'éléments importants à connaître lorsque vous écrivez un agent.

  • Tout d'abord, nous définissons l'agent.

  • Nous définissons donc les instructions système, les garde-fous, la configuration et la manière de l'utiliser.

  • Nous avons également un AgentRunner.

  • AgentRunner est l'élément qui exécute l'agent pour nous.

  • Cela permet de gérer le mécanisme de boucle dont nous parlions.

  • Enfin, nous avons AgentEvents, qui permet au système, à l'API, de nous indiquer ce qu'il fait réellement.

  • Il nous indiquera donc quand il renvoie du texte, quand il effectue un appel d'outil, etc., afin que nous puissions l'afficher dans notre UI.

  • OK.

  • Examinons maintenant le code.

  • OK.

  • Avant d'écrire le code, nous devons d'abord vérifier si l'appel de fonction est disponible sur votre appareil, car il n'est compatible qu'avec Gemini Nano 4.

    val model = Generation.getClient()
    val features = model.getAvailableFeatures()
    if(FUNCTION_CALLING in features) {
      // Device is supported!
    } else {
      // Graceful fallback
    }
  • Tout d'abord, nous devons vérifier en appelant le modèle génératif getAvailableFeatures.

  • Il nous suffit de vérifier si l'appel de fonction est listé dans les fonctionnalités.

  • Si ce n'est pas le cas, nous pouvons implémenter une solution de remplacement progressive.

  • OK.

  • Passons maintenant au code.

    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 }
    
    }
  • Nous savons que l'appel de fonction est compatible avec notre appareil. Nous pouvons donc simplement commencer à créer l'agent à l'aide de Agent.builder.

  • Nous pouvons ensuite définir son nom.

  • Nous pouvons setInstructions pour définir le contexte et le persona.

  • Enfin, nous appelons enableAppFunctions.

  • Cet appel de fonction va donc parcourir et découvrir automatiquement toutes les AppFunctions que nous venons de lister, et nous venons de créer l'agent.

  • Ensuite, nous devons initialiser AgentRunner.

  • Cet exécuteur d'agent gérera la boucle d'exécution pour nous.

  • Nous l'appelons simplement en appelant un constructeur et en lui transmettant l'agent.

  • Enfin, nous pouvons encapsuler la commande vocale transcrite dans un bloc de contenu pour créer la requête.

  • Ensuite, nous appelons l'exécuteur.

  • Nous appelons cette fonction runSingleTurnAsync sur le prompt, ce qui va démarrer l'agent, exécuter l'inférence et effectuer l'inférence d'IA complète.

  • Et si nous voulons seulement diffuser la réponse textuelle finale du modèle dans notre UI, nous appelons simplement textTokens, qui collectera la réponse au fur et à mesure qu'elle arrive.

  • Voilà, c'est tout.

  • Comme vous pouvez le constater, il ne s'agit que de quelques lignes de code. Nous ne plaisantions pas quand nous l'avons dit.

  • Avec seulement ces quelques lignes de code, vous disposez d'un agent.

  • Cet agent fera ce que vous voulez.

  • Il nous donnera la réponse textuelle.

  • Mais bien sûr, dans cette première démonstration, nous n'avons exécuté qu'une commande très simple.

  • J'ai exécuté une commande, et il a effectué une action, c'est tout. Ce n'est donc pas encore un agent à part entière, n'est-ce pas ?

  • Alors, pimentons un peu les choses.

  • Pour la suite, je veux donner une commande, mais je veux la rendre un peu plus difficile pour l'agent.

Architecture et exécution d'un agent multi-étapes

  • Revenons à mon appareil de démonstration et voyons si nous pouvons rendre les choses un peu plus difficiles.

  • Nous y sommes déjà.

  • Parfait.

  • Comme je l'ai dit, nous avons prévu de visiter un musée demain.

  • Si j'ouvre l'activité "Visite du musée", je peux voir que le prix d'entrée est de 25 $.

  • Voyons maintenant si notre agent est capable de le comprendre par lui-même.

  • Je reviens donc à mon écran de dépenses et je lui donne une commande : "Ajoute mon billet de musée à mes dépenses."

  • Bien.

  • Comme vous pouvez le voir, il vérifie mon itinéraire, trouve la visite du musée et l'ajoute à mes dépenses.

  • Ici, il a fallu trois étapes pour atteindre l'objectif, et j'ai également obtenu une réponse textuelle finale.

  • Encore une fois, tout cela fonctionne automatiquement.

  • Vous avez vu la vitesse réelle de ce modèle sur l'appareil.

  • Revenons aux diapositives pour voir comment cela fonctionne.

  • Comme vous pouvez le voir, nous avons légèrement modifié les choses par rapport à notre première démonstration.

  • Dans ce cas, comme vous pouvez le voir, je ne lui ai pas parlé du musée ni des dépenses. Ainsi, lorsque le modèle a reçu la requête et les outils, il a essayé de décider quoi faire en premier.

  • Ici, il décide qu'il a besoin d'en savoir plus sur mon itinéraire actuel. Il va donc appeler getItinerary.

  • Il obtient une réponse et sait alors qu'il y a une visite de musée dans cet itinéraire, et que c'est probablement celle dont elle parlait.

  • Recherchons donc les détails de cet événement spécifique.

  • Parmi toutes les autres métadonnées de cet événement spécifique, il va voir qu'il s'agit de 25 $.

  • Il comprend, revient en arrière et comprend qu'il doit appeler l'outil "add an expense".

  • Ici, nous voyons plusieurs étapes se dérouler l'une après l'autre.

  • Victoria, pouvez-vous nous en dire un peu plus à ce sujet ?

  • Oui.

  • Ce que Jolanda vient de décrire est un exemple concret de la démonstration qu'elle vient de présenter. Je vais maintenant passer à un niveau d'abstraction plus conceptuel.

  • Cela se produit donc chaque fois que nous effectuons un appel de fonction en plusieurs étapes.

  • Tout d'abord, voici notre application, et voici le modèle Gemini Nano.

  • L'utilisateur commence par envoyer la requête et la liste des outils disponibles au modèle.

  • Le modèle utilisera ensuite ces informations pour décider de ce qu'il doit renvoyer.

  • Imaginons que, dans le premier cas, il renvoie un appel d'outil, que nous appellerons "Appel d'outil 1".

  • Ensuite, l'application exécute cet appel d'outil et obtient une réponse d'outil.

  • Ensuite, il renvoie au modèle l'historique complet de la conversation, qui inclut la requête d'origine, la liste des outils, le premier appel d'outil et la première réponse d'outil.

  • Ensuite, le modèle utilise à nouveau ces informations pour décider de la suite à donner.

  • Il peut donc continuer à faire des allers-retours jusqu'à ce que nous obtenions le résultat final.

  • Imaginons que le modèle renvoie le nième appel d'outil, puis que l'application renvoie à nouveau l'intégralité de l'historique des conversations.

  • Une fois que le modèle est satisfait, c'est-à-dire qu'il sait que la requête est satisfaite, il envoie une réponse finale à l'application, qui peut alors afficher dans l'UI que l'opération est terminée.

  • OK.

  • Je vais maintenant vous expliquer comment implémenter cela dans le code.

    /**
    * 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 { ... }
  • Vous vous souvenez de l'exemple de code en une seule étape ? Voici le code en plusieurs étapes. Il est très semblable, et le plus important est d'écrire le KDoc de manière très claire.

  • Dans cet exemple, la fonction getItinerary indique explicitement qu'elle ne renvoie qu'un résumé général des événements.

  • Nous avons également mentionné que nous devions appeler getEventDetails avec un eventId si nous voulions obtenir des détails plus spécifiques sur le trajet.

  • Ensuite, getEventDetails comporte également le commentaire KDoc, qui indique qu'il peut récupérer des détails, tels que les prix des billets et d'autres notes.

  • Ainsi, en utilisant le commentaire et en le décrivant clairement, nous pouvons guider le modèle pour qu'il comprenne comment les outils interagissent entre eux.

  • OK.

  • C'est ainsi que nous créons l'agent pour qu'il effectue toute cette boucle, cette boucle d'agent.

    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
     }
    }
  • Il ressemble beaucoup à l'exemple en une seule étape, avec seulement deux ajouts.

  • Tout d'abord, nous devons définir le nombre maximal de tours. Dans cet exemple, nous l'avons défini sur cinq.

  • Alors, que fait-il ?

  • Cela empêche le modèle de tourner en boucle indéfiniment.

  • Par exemple, si la requête est ambiguë ou si l'agent appelle un outil, puis un autre, puis revient au premier, et ainsi de suite, cette option permet d'arrêter la boucle.

  • Ensuite, nous appelons toujours la même fonction pour l'exécuter, runSingleTurnAsync.

  • Ainsi, même si on parle de "tour unique", un tour correspond en réalité à une requête utilisateur.

  • L'utilisateur final s'attend donc à ce que j'envoie une requête et que vous renvoyiez une réponse, sans vraiment se soucier de ce qui s'est passé en arrière-plan, du nombre de boucles, etc.

  • Nous pouvons donc continuer à utiliser la même fonction.

  • De plus, comme l'appel d'outils en plusieurs étapes prend un peu plus de temps, vous avez également vu dans la démo qu'il indique la fonction qu'il appelle actuellement, ce qui est en cours d'exécution et si l'opération est terminée.

  • Nous voulons donc afficher la progression en direct dans l'UI.

  • Ainsi, au lieu de simplement collecter la réponse textuelle, nous pouvons afficher le nom de l'outil, event.name, chaque fois que AgentEvent émet un FunctionCall ou un FunctionResponse. Une fois l'opération terminée, nous pouvons simplement renvoyer TextMessage comme auparavant.

Gérer les sessions de conversation multitours

  • Excellent !

  • Nous appelons donc cette exécution asynchrone "exécution asynchrone à un seul tour", ce qui, bien sûr, suppose qu'il existe également un autre type d'API, n'est-ce pas ?

  • Oui.

  • Bien sûr, une seule étape ne suffit pas.

  • Nous voulons effectuer une requête en plusieurs étapes.

  • Passons maintenant aux sessions multitours. Je pense que nous pouvons revenir à ma démonstration et la rendre un peu plus difficile.

  • Nous avons parlé de la visite du musée, mais nous avions également la croisière dans la baie du Golden Gate.

  • Pour celui-ci, je veux juste savoir si j'ai assez de budget avant de le réserver.

  • Posons-lui une question.

  • Voyons cela.

  • Je pense emmener mon équipe faire une croisière dans la baie du Golden Gate demain, et je pensais que ça coûtait environ 180 $.

  • Si je fais cela, quel sera mon budget restant ?

  • Alors, que fait-il ?

  • Il répond à la question "Quel sera mon budget restant ?".

  • Il comprend donc l'itinéraire actuel.

  • Il dispose de ces informations et peut les communiquer.

  • Mais je peux aussi poser une question complémentaire.

  • OK, c'est bien.

  • Peux-tu l'ajouter à mes dépenses ?

  • Voilà, c'est fait.

  • Ah, il faut une devise. C'est l'effet démo.

  • OK, donc c'est en dollars.

  • 180 $.

  • Bien.

  • C'est la première fois que ça arrive, c'est cool.

  • D'accord, mais que fait-il ?

  • Il m'indique qu'il a fallu trois tours et qu'il a ajouté les dépenses. Il me permet également d'actualiser cette session.

  • J'ai un petit bouton d'actualisation en haut. Si je clique dessus, j'efface tout l'historique et je peux recommencer.

  • Je pense que nous pouvons maintenant revenir à nos diapositives.

  • Nous avons des démonstrations fonctionnelles qui font toutes ce que nous voulons. Je pense donc que nous pouvons revenir au code.

  • Oui.

  • C'est vraiment génial.

  • Vous remarquerez que Jolanda n'a pas mentionné qu'il s'agissait d'un billet pour la croisière Golden Gate Bay. Elle a simplement indiqué qu'il s'agissait d'un montant en dollars.

  • Alors, comment le modèle connaît-il tout le contexte ?

  • C'est donc un peu différent de ce que je vous ai montré dans les deux extraits de code précédents.

    // 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()
  • Au lieu d'utiliser AgentRunner et runSingleTurnAsync, nous devons créer une session.

  • Ici, nous appelons runner.getOrCreateSession avec tripId.

  • Pourquoi devons-nous créer une session ?

  • En effet, runSingleTurnAsync réinitialise la mémoire, ce qui signifie qu'il n'aura pas de contexte.

  • Nous ne pourrons pas avoir de conversation multitour.

  • Ainsi, lorsque vous souhaitez avoir une conversation multitour, nous utilisons la session, la session de l'agent.

  • Nous transmettons ensuite tripId.

  • Pourquoi devons-nous faire cela ?

  • Comme vous l'avez déjà vu, l'application Jetpacker gère plusieurs voyages.

  • Il y a un voyage ADS, un voyage à Paris, un voyage à Tokyo peut-être.

  • Nous voulons donc conserver le contexte du trajet.

  • On ne veut pas se mettre des bâtons dans les roues, tu vois ?

  • Ensuite, au lieu d'appeler runSingleTurnAsync, nous avons runAsync.

  • C'est donc différent.

  • Cette commande est donc exécutée sur la session, et nous avons toujours la même requête.

  • Nous pouvons ensuite émettre AgentEvent, FunctionCall, FunctionResponse et TextMessage comme avant. C'est la même chose.

  • Et lorsque Jolanda a cliqué sur le bouton de nouvelle conversation, le bouton d'actualisation, nous pouvons avoir session.delete.

  • Oui.

  • OK.

  • Nous avons donc fait tout cela avec les nouvelles API d'agent, ce qui est formidable et fonctionnera à merveille pour bon nombre de vos cas d'utilisation.

  • Bien sûr, nous voulons toujours vous permettre de faire des choses rapidement ou facilement, mais nous voulons aussi vous permettre de gérer des cas plus complexes.

  • Nous voulons donc nous assurer que, si nécessaire, vous pouvez toujours utiliser les API d'accès direct pour créer une version de niveau inférieur de ces éléments.

  • Victoria, peux-tu nous expliquer ce que cela signifie et quand nous devrions revenir à ce niveau d'accès direct ?

Workflows agentiques avancés et intégration d'outils

  • Oui, bien sûr, Jolanda.

  • Vous connaissez peut-être déjà l'API Prompt, qui permet cet accès direct.

  • Nous avons donc également pris en charge l'appel de fonction ici, et nous avons deux cas d'utilisation où nous préférons utiliser cette API plutôt que le flux agentique plus simple.

  • Le premier est un cas d'utilisation très simple.

  • Vous vous souvenez que dans le flux agentique, nous renvoyons l'intégralité de la conversation, la requête, les outils, l'appel d'outil, la réponse de l'outil, etc. au modèle.

  • Dans certains cas d'utilisation, nous ne voulons pas faire cela, nous voulons simplement envoyer la requête, par exemple.

  • Imaginons que nous voulions envoyer une requête au modèle qui renvoie un appel d'outil, mais que nous ne voulions pas renvoyer l'appel d'outil. Nous voulons simplement envoyer une autre requête au modèle. Nous pouvons alors modifier l'historique, car nous en avons le contrôle total.

  • Voici un exemple plus concret.

    // 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()
    )
  • Imaginons que nous ayons une application qui peut modifier le thème d'arrière-plan de votre appareil.

  • Imaginons que l'utilisateur envoie la requête suivante : "Change l'arrière-plan pour un coucher de soleil".

  • Ensuite, notre application a exécuté cette fonction, et l'arrière-plan est devenu un coucher de soleil.

  • Ensuite, l'utilisateur a changé d'avis et a dit : "Oh, non, je voulais dire une plage."

  • Dans ce cas, nous n'avons pas besoin d'envoyer l'état complet indiquant que l'appareil a déjà remplacé l'arrière-plan par un coucher de soleil, puis qu'il souhaite maintenant le remplacer par une plage.

  • Non, ce n'est pas nécessaire.

  • Il nous suffit d'envoyer le premier et le deuxième prompt, car le premier contient les informations d'un arrière-plan.

  • Il comprend donc qu'il doit remplacer l'arrière-plan par une plage.

  • Nous pouvons donc utiliser l'API Prompt pour cela.

  • Nous avons un contrôle total sur l'historique. Nous enregistrons le jeton de contexte sur l'appareil, car la fenêtre de jetons est plus limitée sur l'appareil, ce qui nous permet d'enregistrer plus de jetons.

  • Le deuxième cas d'utilisation est l'extrême opposé.

  • Si nous avons des cas d'utilisation complexes que l'API agentique ne peut pas encore prendre en charge, par exemple si nous avons des systèmes multi-agents où plusieurs sous-agents interagissent entre eux pour résoudre une tâche complexe, ou un système hybride où nous avons à la fois des modèles sur l'appareil et des modèles cloud, ou si nous voulons avoir une mémoire et une compression de contexte personnalisées.

  • Cela est lié à l'historique, comme le contrôle total de l'historique que j'ai mentionné précédemment.

  • Imaginons que nous ne voulions pas tout renvoyer au modèle, car la fenêtre de contexte est limitée. Nous voulons simplement résumer la partie la plus importante, ou peut-être la plus récente. Nous avons donc un contrôle total sur ce point, et nous utilisons l'API d'accès direct.

  • Pour ces cas d'utilisation, vous pouvez utiliser des frameworks tels que l'Agent Development Kit (ADK) ou Crew, ou créer votre propre framework d'orchestration personnalisé.

  • OK.

  • Voilà tout ce que nous avions à dire sur l'API agentique.

  • Cependant, il y a un autre point que nous aimerions aborder.

  • Nous avons beaucoup parlé des agents intégrés aux applications, qui vous permettent de créer votre propre expérience d'agent dans votre application. Mais nous avons aussi parlé de l'utilisation d'AppFunctions. Pourquoi utiliser AppFunctions si vous n'agissez que dans votre propre application ?

  • Nous savons que les développeurs comme vous n'aiment pas vraiment faire les choses deux fois, n'est-ce pas ?

  • Nous n'aimons pas copier-coller du code, ni devoir dupliquer une logique métier.

  • Ainsi, avec AppFunctions, au lieu de n'autoriser que notre agent intégré à l'application à les utiliser, nous pouvons, grâce à une modification très simple, mettre nos outils à la disposition des agents système.

  • À quoi cela ressemble-t-il au niveau du code ?

  • Oui.

  • Avec AppFunction, il suffit d'écrire une seule fois les fonctions d'application, puis de les exposer à différents niveaux de couches, selon nos besoins.

  • Vous vous souvenez que nous avions auparavant ACCESS_LEVEL_SELF, qui n'exposait les fonctions que nous avions écrites qu'à notre agent intégré à l'application, mais nous pouvons également modifier ce niveau d'accès en ACCESS_LEVEL_ANDROID_TRUSTED.

  • Cela exposera les fonctions aux agents de confiance de l'OS Android.

  • Vous n'avez donc pas besoin de réécrire les fonctions, les commandes ni la logique de la base de données.

  • AppFunctions agit déjà comme la voix de votre application, en interne comme en externe.

  • Nous voulons aussi une démonstration de cela, n'est-ce pas ?

  • Revenons au téléphone. Pour notre dernière démonstration, nous allons quitter l'application Jetpacker.

  • Nous pouvons donc quitter cette page et accéder à Gemini.

  • Dans Gemini, nous pouvons donc appeler @Jetpacker.

  • Notez que cette fonctionnalité n'est disponible qu'en version bêta limitée. Elle ne sera donc pas accessible à tout le monde dès aujourd'hui, mais il est possible de la tester et de l'essayer.

  • Voyons ce que ça donne avec Jetpacker.

  • Comment devons-nous procéder ?

  • Oh, j'ai bu un café ce matin, n'est-ce pas ?

  • Peux-tu ajouter un café à 7 $ aux frais de déplacement ADS, s'il te plaît ?

  • Bien.

  • Comme vous pouvez le voir, il se connecte à l'application Jetpacker.

  • Et voilà !

  • Il a ajouté la dépense pour moi.

  • Nous avons vu comment utiliser tous les éléments de Jetpacker.

  • L'avantage de Jetpacker, c'est que nous pouvons également activer le mode Avion.

  • Nous ne l'avons pas vraiment montré aujourd'hui, mais nous pouvons simplement activer le mode Avion, et tout ce que vous avez vu dans les démonstrations, et je veux en quelque sorte le montrer, car je veux m'assurer que vous me faites tous confiance, alors désactivons le Wi-Fi.

  • Quelqu'un a-t-il un bon exemple de démonstration, une dépense que je peux ajouter ?

  • Voyons cela.

  • On pourrait peut-être faire "Qu'est-ce que c'est ?"

  • Oh, la location de voiture serait bien, n'est-ce pas ?

  • OK.

  • Disons : "Ajoute une dépense de 300 $ pour la location de voiture."

  • Et voilà, il l'a trouvé.

  • OK.

  • Parfait.

  • Revenons maintenant aux diapositives.

  • Nous parlions d'AppFunctions.

  • Je sais que nous l'avons abordé très brièvement.

  • Si vous souhaitez en savoir plus, Dan et Sarah ont également donné une autre conférence dans laquelle ils expliquent plus en détail comment activer et tester cette fonctionnalité, quels outils sont disponibles, etc.

  • Aujourd'hui, nous avons abordé de nombreux sujets.

  • Nous avons vu les étapes uniques, les étapes multiples, les tours multiples et, enfin, comment utiliser l'AppFunction système.

  • Maintenant, je pense que nous avons quelques conseils à vous donner avant de partir.

Bonnes pratiques et conclusion

  • Oui.

  • Gardez donc ces trois conseils à l'esprit lorsque vous commencerez à créer ces expériences agentiques.

  • Le premier est de garder l'accent sur les ensembles d'outils.

  • N'oubliez pas que les modèles sur l'appareil ont des capacités limitées en raison de leur taille réduite.

  • Au lieu d'enregistrer des dizaines d'outils, il est donc préférable d'en utiliser un nombre plus restreint.

  • Comme je l'ai déjà dit, rédigez des descriptions KDoc claires, car c'est ce que le modèle utilise pour décider de ce qu'il va renvoyer.

  • Nous devons également définir un garde-fou pour le nombre maximal de tours.

  • Cela empêche également le modèle de tourner en boucle s'il est confus.

  • Cela signifie que nous risquons de décharger la batterie de l'utilisateur.

  • Si nous disposons de cette information, nous pouvons économiser la batterie de l'utilisateur.

  • Enfin, nous devons vérifier la disponibilité de l'appel de fonction sur l'appareil et implémenter une solution de remplacement appropriée.

  • Par exemple, si elle n'est pas compatible, vous pouvez vous référer aux modèles cloud ou à votre UI standard.

  • Je pense que nous pouvons clore ce sujet avec nos ressources.

  • Je vous montre donc une dernière fois les QR codes.

  • Veuillez y jeter un œil.

  • Je pense qu'il faut garder à l'esprit que nous avons également les Android Skills pour cela.

  • Vous n'avez donc pas besoin d'examiner chaque détail en profondeur.

  • Nous disposons d'une documentation complète et d'une compétence Android pour accompagner cette nouvelle API d'agent.

  • Jetons-y un coup d'œil.

  • Téléchargez la Preview développeur AICore pour tester toutes ces fonctionnalités. Bon codage !

Code

Extraits de code

Extrait Kotlin à 09:53 dans "Jetpacker agent demo and function calling"
/**
 * 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...
}
Extrait Kotlin à 11:41 dans "Créer et implémenter un exécuteur d'agent IA"
val model = Generation.getClient()
val features = model.getAvailableFeatures()
if(FUNCTION_CALLING in features) {
  // Device is supported!
} else {
  // Graceful fallback
}
Extrait Kotlin à 12:07 dans "Créer et implémenter un exécuteur d'agent IA"
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 }

}
Extrait Kotlin à 17:23 dans "Architecture et exécution d'un agent en plusieurs étapes"
/**
* 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 { ... }
Extrait de code Kotlin à 18:23 dans "Architecture et exécution d'un agent en plusieurs étapes"
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
 }
}
Extrait Kotlin à 22:13 dans "Gérer les sessions de conversation multitours"
// 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()
Extrait Kotlin à 25:02 dans "Workflows agentiques avancés et intégration d'outils"
// 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()
)