Intelligente Anwendungen

Agentische Funktionen in Ihrer App mit ML Kit GenAI freischalten

Lautsprecher

Info

Über den Vortrag

Mit autonomen On-Device-Agenten können Sie die KI-Funktionen von Android noch besser nutzen. In dieser Session beschäftigen wir uns mit den neuen agentischen APIs von ML Kit, die direkt auf den APIs für den direkten Modellzugriff aufbauen, die Sie bereits kennen.

Anhand der Open-Source-App Jetpacker erfahren Sie, wie Sie mehrstufige Aktionen über lokale Daten hinweg verketten, zustandsorientierte Multi-Turn-Sitzungen mit isolierten Kontexten verwalten und Android MCP so einbinden, dass sowohl In-App-Assistenten als auch Android-Systemoberflächen mit einer einzigen Codebasis unterstützt werden.

Zusammenfassung

  • Gemini Nano 4 unterstützt Funktionsaufrufe nativ. So können On-Device-Modelle natürliche Sprache in Aktionen übersetzen und lokale APIs steuern.
  • In der Jetpack AppFunctions-Bibliothek werden Tools durch KDoc-Beschreibungen in natürlicher Sprache definiert. Wenn Sie die Zugriffsebenen ändern, können diese Funktionen für System-Agents wie Gemini zugänglich gemacht werden.
  • Die neue Agentic Flows-Bibliothek (Alpha) in ML Kit GenAI vereinfacht die Erstellung von KI-Agenten, da sie die Ausführungsschleife und Toolaufrufe übernimmt.
  • AgentRunner koordiniert die Ausführung von Tools und bietet Sitzungsmethoden für Multi-Turn-Unterhaltungen, ohne den Kontext zu verlieren.
  • Der direkte Zugriff über die Prompt API ist weiterhin für die einfache Verlaufskontrolle oder komplexe Setups wie Multi-Agent- und Hybridsysteme verfügbar.

Fähigkeiten

Ressourcen

Gemini Notebook

Themen

  • ml-kit
  • on-device-ai
  • gemini-nano
  • ai-agents
  • Funktionsaufrufe
  • app-functions
  • aicore

Transkript

Einführung in On-Device-KI und Gemini Nano

  • Mein Name ist Jolanda Verhoef.

  • Ich bin Developer Relations Engineer bei Android und heute ist Victoria Lestari vom AICore-Entwicklerteam bei mir.

  • Hallo zusammen!

  • Vielen Dank, dass Sie heute Morgen dabei sind.

  • Deshalb arbeiten unsere Teams seit Jahren eng zusammen, um hochmoderne KI-Modelle aus der Cloud direkt auf Android-Geräte zu bringen.

  • Diese Bemühungen sind in der ML Kit GenAI-Suite zusammengefasst.

  • Einerseits bietet die ML Kit GenAI Suite High-Level-APIs für schlüsselfertige Lösungen, z. B. für Textzusammenfassungen, Bildbeschreibungen und Spracherkennung.

  • Außerdem haben wir den direkten Zugriff auf das On-Device-Modell Gemini Nano ermöglicht.

  • Heute gehen wir sogar noch einen Schritt weiter.

  • Mit diesem direkten Zugriff können Sie In-App-Agenten erstellen, die über mehrere Runden hinweg logisch denken, den Kontext der Unterhaltung speichern und Aktionen in Ihrer App ausführen können.

  • Was meinen wir damit, Victoria?

  • Ja.

  • Bisher haben wir bei On-Device-Anwendungsfällen an kürzere, einfachere, isolierte Aufgaben gedacht, z. B. das Erkennen eines wahrscheinlichen Betrugs oder das Zusammenfassen eines Telefonats.

  • Wenn wir komplexere Funktionen entwickeln möchten, wie etwa Fragen zu Ihrem Fotokatalog oder Fragen an Google Maps, greifen wir natürlich auf große Cloud-Modelle zurück.

  • Cloud-Modelle bieten also den Vorteil einer enormen Rechenleistung und von Echtzeitdaten, aber On-Device-KI eröffnet neue Möglichkeiten für eine niedrige Latenz und eine Echtzeit- und lokale Datenverarbeitung.

  • Gemini Nano hat also bereits die Möglichkeiten auf Ihren Geräten erweitert.

  • Ein Beispiel dafür ist Pixel Screenshots. Damit kannst du lokale Screenshots durchsuchen und Fragen dazu stellen.

  • Wenn wir also KI-Agenten auf dem Gerät ausführen, brauchen wir einen großen Sprung in der Modellleistung. Deshalb entwickeln wir Gemini Nano 4, das in der Lage ist, über mehrere Schritte hinweg zu schlussfolgern und echte Aktionen auszuführen.

  • Diese neue Funktion ist jedoch nur dann relevant, wenn Entwickler echte Geräte haben, auf die sie abzielen können.

  • Die gute Nachricht ist, dass Gemini Nano 4 bereits auf ausgewählten Flaggschiff-Geräten verfügbar ist. Außerdem können Sie es über die AICore-Entwicklervorschau auf vielen weiteren Geräten testen.

  • Außerdem sind wir auf dem besten Weg, Anfang nächsten Jahres 100 Millionen Geräte zu erreichen.

  • Was bedeutet das?

  • Sie können also schon heute mit der Entwicklung und dem Testen dieser Funktionen beginnen und wissen, dass das, was Sie heute entwickeln, für ein riesiges Publikum skalierbar sein wird.

  • Wir haben also über Gemini Nano 4 und die potenzielle Reichweite gesprochen. Jetzt wollen wir uns ansehen, was agentische Funktionen eigentlich bedeuten.

  • Agentische KI kann also komplexe Aufgaben planen, diese in mehrere Schritte unterteilen und diese Schritte dann in Ihrer App ausführen.

  • Unser On-Device-Modell Gemini Nano kann beispielsweise Ihre lokale Datenbank abfragen, Daten aus dem Backend abrufen, Ihre komplexen Fragen analysieren und beantworten sowie systemeigene App-Workflows auf Ihrem Gerät auslösen.

  • Aber wie geht eine KI eigentlich von der Planung zur Ausführung über?

  • Und das ist das Thema für heute: Funktionsaufrufe.

  • Sie ist die zentrale Engine für agentische Funktionen.

  • Es übersetzt natürliche Sprache in Systemausführung und wandelt das Modell von passiven Überlegungen in aktive Ausführung um, indem es die lokalen APIs auswählt und steuert, die zur Lösung der komplexen Aufgaben erforderlich sind.

  • Außerdem freuen wir uns, Ihnen mitteilen zu können, dass Funktionsaufrufe nativ auf Gemini Nano 4 unterstützt werden.

  • Die Funktion ist derzeit in der AICore-Entwicklervorschau verfügbar und wird demnächst auf allen Geräten eingeführt, die Nano 4 unterstützen.

  • Es ist also toll, dass wir diese Art von Funktionen im Modell haben, aber eine Funktion in einem Modell bietet Ihnen als Entwickler noch keine APIs.

  • Wir haben also auch daran gearbeitet, die richtigen APIs zur ML Kit GenAI-Suite hinzuzufügen, damit das Erstellen eines Agents tatsächlich ein Kinderspiel wird.

  • Wir haben mit den bereits vorhandenen Direct Access APIs begonnen und Funktionstypen und Typen für den Unterhaltungsverlauf hinzugefügt.

  • Mit diesen beiden neuen Ergänzungen der Prompt API des ML Kit können Sie diese Low-Level-Agentenfunktionen jetzt in Ihre App einbinden.

  • Allerdings musst du dann ziemlich viel selbst machen.

  • Sie müssen die Funktionen also manuell ausführen, die Anrufliste speichern und mehrere Anrufe durchlaufen.

  • Wenn Sie also mehrere Aufrufe ausführen müssen, müssen Sie selbst eine Art Schleifensystem erstellen und eigene Schutzmaßnahmen für das System einrichten.

  • Das ist möglich, aber wir möchten es natürlich schnell und einfach machen.

  • Zusätzlich zu dieser Prompt API-Bibliothek haben wir auch eine neue Bibliothek für agentische Abläufe hinzugefügt.

  • Sie finden es mit com.mlkit.genai:agent-api und es ist derzeit als Alphaversion verfügbar.

  • Mit dieser neuen API übernehmen wir einen Großteil der Arbeit für Sie.

  • Anstatt viele manuelle Schritte auszuführen, können Sie mit nur wenigen Codezeilen einen KI-Agenten erstellen.

  • Und genau das werden wir heute tun.

Jetpacker-Agent-Demo und Funktionsaufrufe

  • Wir sehen uns jetzt an, wie diese APIs in unserer Beispiel-App Jetpacker funktionieren.

  • Sie haben Jetpacker bereits auf der Google I/O und im Rahmen dieser Konferenz gesehen.

  • Sie können aber diesen QR-Code scannen und sich den Code ansehen, wenn Sie möchten. Sie können Ihren Agenten auf den Code verweisen, mit dem Code interagieren und mehr darüber erfahren, wie wir all diese Funktionen implementiert haben.

  • Jetpacker ist ein moderner Reiseassistent, den wir entwickelt haben, um viele verschiedene intelligente Funktionen zu präsentieren.

  • Es verwendet also bereits On-Device- und Cloud-Modelle für alle Arten von Prompting APIs. Jetzt fügen wir einen Agenten hinzu.

  • Für diesen Agenten, Jetpacker, verwenden wir die neue ML Kit GenAI Agent API, über die wir gerade gesprochen haben. Diese verwendet das zugrunde liegende AI Core-System, das auf Android-Geräten ausgeführt wird, um mit dem Gemini Nano-Modell zu kommunizieren.

  • Ich kann viel erzählen – und das werde ich auch –, aber natürlich möchten wir Ihnen das auch zeigen.

  • Wechseln wir also zum Smartphone und sehen uns das in einer Demo an.

  • Ich habe hier ein Pixel 11 Pro.

  • Ich denke, wir können zum Pro wechseln, und du solltest es sehen können.

  • So, da ist es.

  • Wie Sie sehen, wird hier die Jetpacker-App ausgeführt.

  • Hier werden einige unserer anstehenden Reisen angezeigt. Ich kann mir die aktuelle Reise ansehen und sehe, dass heute der 29. Oktober ist. Wir haben also unseren On-Device-KI-Workshop und die Demo-Präsentation. Morgen stehen dann eine Golden Gate Bay Cruise und ein Museumsbesuch an.

  • Das ist alles großartig.

  • Jetzt können wir den Bildschirm „Ausgaben“ aufrufen, auf dem wir das Budget für diese Reise im Blick behalten.

  • Und wie Sie rechts unten sehen, gibt es dieses neue kleine Agentensymbol. Das ist der On-Device-Agent, den wir entwickelt haben.

  • Beginnen wir mit einem ziemlich einfachen Befehl.

  • Ich glaube, wir können es bitten, eine bestimmte Ausgabe hinzuzufügen.

  • Sagen wir also: „Füge eine Ausgabe für das Taxi nach Mountain View hinzu, es waren 45 $.“

  • Na also.

  • Die Ausgabe wird vorbereitet und hinzugefügt.

  • Prima.

  • Okay, Demo 1 hat funktioniert.

  • Sehen wir uns auf den Folien an, wie das funktioniert hat.

  • Zuerst einmal habe ich, wie Sie vielleicht bemerkt haben, meine Stimme verwendet. Diese Stimme wird tatsächlich auch vom Gemini Nano-Modell verwendet, das die Speech Recognition API nutzt, um das, was ich gesagt habe, in Text zu transkribieren.

  • Dann ist der Text gut und wir können ihn an das Gemini Nano-Modell senden.

  • Aber was würde es tun?

  • Ich sage ihm, dass es etwas zu den Ausgaben hinzufügen soll, und ich sage ihm etwas über 45 $, aber es weiß nicht wirklich, was es tun soll, oder?

  • An dieser Stelle wird es mir nur sagen: „Ich weiß nicht, wovon du sprichst.“

  • Wir möchten den Prompt also etwas erweitern.

  • Wir können dem Prompt Tools hinzufügen, also alle möglichen Tools. In diesem Fall füge ich das Reisebudget, den Reiseplan usw. hinzu. Wenn Sie dies mit unseren APIs an das Nano-Modell weitergeben, versteht es automatisch, dass es jetzt einen Funktionsaufruf anstelle von Text zurückgeben kann.

  • In unserem Fall wird also gesagt: „Anstatt mit Text zu antworten, füge ich eine Ausgabe von 45 $ für das Taxi nach Mountain View hinzu.“

  • Das ist toll, aber wie funktioniert das eigentlich, Victoria?

  • Wie bauen wir das?

  • Ja.

  • Wie definieren wir also die Tools in unserem Code?

  • Das werde ich Ihnen jetzt erklären.

  • Mit Android MCP verwenden wir die Jetpack-Bibliothek „AppFunctions“, um diese Tools zu definieren und zu veröffentlichen.

  • Die AppFunctions-Bibliothek kann die Dienste, Daten und Aktionen Ihrer App in der Android-Betriebssystem-Registry verfügbar machen, sodass wir genau dieselben Funktionen verwenden können, um die Tools zu deklarieren und zu beschreiben und sie in Ihrer App zu verwenden.

  • Außerdem können Nutzer diese Aufgabe über Agenten und Interaktionen auf Systemebene erledigen.

  • Ich gebe Ihnen dazu ein konkretes Beispiel im 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...
    }
  • Das ist also ein Beispiel für ein AppFunctionDeclaration.

  • Der KDoc-Kommentar oben beschreibt in natürlicher Sprache, was die Funktion tut und welche Parameter sie hat.

  • Es empfiehlt sich daher, eine möglichst klare Beschreibung anzugeben, da diese an das Modell gesendet wird.

  • Darunter sehen Sie eine Annotation, @AppFunctionAccessLevel.

  • Damit wird die Sichtbarkeit der Funktion eingeschränkt.

  • In diesem Beispiel haben wir ACCESS_LEVEL_SELF. Das bedeutet, dass die von uns geschriebenen Funktionen nur für Ihre Anwendung und die Agenten Ihrer Anwendung sichtbar sind.

  • Anschließend können wir die Sperrfunktion selbst definieren und implementieren. Wir können so viele App-Funktionen wie möglich schreiben und sie alle in einer einzigen Dienstklasse gruppieren.

KI-Agenten-Runner erstellen und implementieren

  • Okay.

  • Wir haben also die Tools und können sie schreiben. Das ist großartig.

  • Um einen Agenten zu erstellen, benötigen wir jedoch etwas mehr als nur diese AppFunctions.

  • Es gibt drei weitere wichtige Komponenten, die Sie kennen sollten, wenn Sie einen KI-Agenten erstellen.

  • Zuerst definieren wir den Agenten.

  • Wir definieren also die Systemanweisungen, die Guardrails, die Konfiguration und die Art und Weise, wie damit gearbeitet werden soll.

  • Und dann haben wir noch einen AgentRunner.

  • Der AgentRunner führt den Agenten für uns aus.

  • Damit wird der Schleifenmechanismus, über den wir gesprochen haben, berücksichtigt.

  • Und schließlich gibt es noch AgentEvents. Damit kann das System, also die API, uns mitteilen, was es gerade tut.

  • Es wird uns also mitteilen, wenn es Text zurückgibt, wenn es einen Toolaufruf durchführt usw., damit wir das dann tatsächlich in unserer Benutzeroberfläche anzeigen können.

  • Okay.

  • Sehen wir uns den Code an.

  • Okay.

  • Bevor wir mit dem Schreiben des Codes fortfahren, müssen wir zuerst prüfen, ob Funktionsaufrufe auf Ihrem Gerät verfügbar sind, da diese nur auf Gemini Nano 4 unterstützt werden.

    val model = Generation.getClient()
    val features = model.getAvailableFeatures()
    if(FUNCTION_CALLING in features) {
      // Device is supported!
    } else {
      // Graceful fallback
    }
  • Zuerst müssen wir das generative Modell getAvailableFeatures aufrufen, um die Prüfung durchzuführen.

  • Wir können einfach prüfen, ob Funktionsaufrufe in den Funktionen aufgeführt sind.

  • Andernfalls können wir ein risikominimiertes Fallback implementieren.

  • Okay.

  • Sehen wir uns nun den Code an.

    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 }
    
    }
  • Wir wissen, dass Funktionsaufrufe auf unserem Gerät unterstützt werden, und können dann einfach mit dem Erstellen des KI-Agenten beginnen, indem wir Agent.builder verwenden.

  • Dann können wir den Namen festlegen.

  • Wir können setInstructions verwenden, um den Kontext und die Persona zu definieren.

  • Schließlich rufen wir enableAppFunctions auf.

  • Diese Funktion durchläuft also alle AppFunctions, die wir zuvor aufgelistet haben, und erkennt sie automatisch. Wir haben den Agenten gerade erstellt.

  • Als Nächstes müssen wir AgentRunner initialisieren.

  • Dieser Agent Runner verwaltet die Ausführungsschleife für uns.

  • Wir rufen ihn einfach auf, indem wir einen Konstruktor aufrufen und den Agenten an ihn übergeben.

  • Schließlich können wir den transkribierten Sprachbefehl in einen Inhaltsblock einfügen, um den Prompt zu erstellen.

  • Und dann rufen wir den Runner auf.

  • Wir rufen diese Funktion runSingleTurnAsync für den Prompt auf. Dadurch wird der Agent gestartet, die Inferenz ausgeführt und die gesamte KI-Inferenz durchgeführt.

  • Wenn wir nur die endgültige Textantwort des Modells in unsere Benutzeroberfläche streamen möchten, rufen wir einfach textTokens auf. Die Antwort wird dann erfasst, sobald sie eintrifft.

  • Das wars.

  • Das sind tatsächlich nur ein paar Zeilen Code. Wir haben also nicht übertrieben, als wir das gesagt haben.

  • Mit nur diesen wenigen Codezeilen haben Sie also einen Agenten.

  • Dieser KI-Agent tut, was Sie möchten.

  • Es wird eine Textantwort ausgegeben.

  • In der ersten Demo haben wir nur einen sehr einfachen Befehl ausgeführt.

  • Ich habe einen Befehl gegeben und es hat eine Aktion ausgeführt, das war alles. Es ist also noch kein vollwertiger Agent, oder?

  • Lassen Sie uns die Sache also etwas aufpeppen.

  • Als Nächstes möchte ich einen Befehl geben, es dem KI-Agenten aber etwas schwerer machen.

Architektur und Ausführung von mehrstufigen Agenten

  • Gehen wir also zurück zu meinem Demogerät und sehen wir uns an, ob wir es ein wenig schwieriger machen können.

  • Wir sind schon da.

  • Prima.

  • Wie gesagt, wir haben für morgen einen Museumsbesuch geplant.

  • Wenn ich den Museumseintrag öffne, sehe ich, dass der Eintrittspreis 25 $ beträgt.

  • Sehen wir uns nun an, ob unser Agent das tatsächlich selbst herausfinden kann.

  • Ich gehe also zurück zu meinem Ausgabenbildschirm und gebe einen Befehl ein: „Füge mein Museumsticket zu meinen Ausgaben hinzu.“

  • Na also.

  • Wie Sie sehen, wird jetzt mein Reiseplan geprüft, der Museumsbesuch gefunden und meinen Ausgaben hinzugefügt.

  • Hier sind also drei Schritte erforderlich, um das Ziel zu erreichen. Außerdem habe ich eine endgültige Textantwort erhalten.

  • Das alles funktioniert automatisch.

  • Sie haben die tatsächliche Geschwindigkeit dieses Modells auf dem Gerät gesehen.

  • Sehen wir uns also die Folien an, um zu verstehen, wie das funktioniert.

  • Wie Sie sehen, haben wir im Vergleich zur ersten Demo ein paar Dinge geändert.

  • In diesem Fall habe ich dem Modell nichts über das Museum oder die Kosten gesagt. Als das Modell den Prompt und die Tools erhalten hat, musste es also entscheiden, was es zuerst tun soll.

  • Hier entscheidet es, dass es mehr über meinen aktuellen Reiseplan wissen muss, und ruft getItinerary auf.

  • Es erhält eine Antwort und weiß dann: „Ah, in diesem Reiseplan ist ein Museumsbesuch enthalten. Das ist wahrscheinlich der, von dem sie gesprochen hat.“

  • Sehen wir uns also die Details dieses Ereignisses an.

  • Und neben allen anderen Metadaten dieses bestimmten Ereignisses wird auch der Wert von 25 $ angezeigt.

  • Er versteht das, geht zurück und weiß, dass er „Ausgabe hinzufügen“ aufrufen muss.

  • Hier sehen wir mehrere Schritte, die nacheinander ausgeführt werden.

  • Victoria, kannst du das etwas genauer erklären?

  • Ja.

  • Was Jolanda gerade beschrieben hat, ist ein konkretes Beispiel für die Demo, die sie gerade gezeigt hat. Ich möchte das Ganze jetzt auf eine etwas konzeptionellere, abstraktere Ebene bringen.

  • Das passiert also jedes Mal, wenn wir einen mehrstufigen Funktionsaufruf ausführen.

  • Das ist unsere App und das ist das Gemini Nano-Modell.

  • Der Nutzer sendet die Anfrage und die Liste der verfügbaren Tools an das Modell.

  • Das Modell verwendet diese Informationen dann, um zu entscheiden, was zurückgegeben werden soll.

  • Nehmen wir an, im ersten Fall wird ein Toolaufruf zurückgegeben, nennen wir ihn Toolaufruf 1.

  • Anschließend führt die App diesen Toolaufruf aus und erhält eine Toolantwort.

  • Anschließend wird der gesamte Unterhaltungsverlauf an das Modell zurückgesendet. Dieser umfasst die ursprüngliche Anfrage, die Liste der Tools, den ersten Toolaufruf und die erste Toolantwort.

  • Das Modell verwendet diese Informationen dann, um zu entscheiden, was als Nächstes zu tun ist.

  • Das kann so lange hin und her gehen, bis wir das endgültige Ergebnis erhalten.

  • Angenommen, das Modell gibt den n-ten Toolaufruf zurück und die Anwendung gibt dann den gesamten Unterhaltungsverlauf zurück.

  • Wenn das Modell zufrieden ist, also weiß, dass die Anfrage erfüllt ist, sendet es eine endgültige Antwort an die App zurück. Die App kann dann in der Benutzeroberfläche anzeigen, dass die Aufgabe erledigt ist.

  • Okay.

  • Ich erkläre jetzt, wie wir das im Code implementieren.

    /**
    * 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 { ... }
  • Sie erinnern sich an das Beispiel aus dem einstufigen Code. Dies ist der mehrstufige Code, der sehr ähnlich aussieht. Am wichtigsten ist es, das KDoc wirklich klar zu schreiben.

  • In diesem Beispiel gibt die Funktion getItinerary also explizit an, dass sie nur eine allgemeine Zusammenfassung der Ereignisse zurückgibt.

  • Wir haben auch erwähnt, dass wir getEventDetails mit einer eventId aufrufen müssen, wenn wir genauere Details zur Fahrt erhalten möchten.

  • Und dann hat die getEventDetails auch den KDoc-Kommentar, der besagt, dass sie Details wie Ticketpreise und andere Hinweise abrufen kann.

  • Durch die Verwendung des Kommentars und die klare Beschreibung des Kommentars können wir dem Modell also zeigen, wie Tools miteinander interagieren.

  • Okay.

  • So erstellen wir den Agenten, um diese Schleife, diese agentische Schleife, auszuführen.

    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
     }
    }
  • Es sieht dem einstufigen Beispiel sehr ähnlich, nur dass hier zwei Ergänzungen vorgenommen wurden.

  • Zuerst müssen wir die maximale Anzahl von Turns festlegen. In diesem Beispiel legen wir fünf fest.

  • Was macht es also?

  • So wird verhindert, dass das Modell in eine Endlosschleife gerät.

  • Wenn die Eingabeaufforderung beispielsweise mehrdeutig ist oder ein Tool nach dem anderen aufgerufen wird, wird die Schleife beendet, wenn wir diesen Wert festlegen.

  • Anschließend rufen wir dieselbe Funktion auf, um sie auszuführen: runSingleTurnAsync.

  • Obwohl es „Single-Turn“ heißt, bedeutet ein Turn eigentlich eine Nutzeranfrage.

  • Der Endnutzer erwartet also, dass er eine Anfrage sendet und eine Antwort erhält, ohne sich wirklich darum zu kümmern, was im Hintergrund passiert, wie oft die Schleife durchlaufen wird usw.

  • Wir können also dieselbe Funktion weiter verwenden.

  • Und weil der mehrstufige Toolaufruf etwas mehr Zeit in Anspruch nimmt, haben Sie in der Demo auch gesehen, dass tatsächlich in Echtzeit angezeigt wird, welche Funktion gerade aufgerufen und ausgeführt wird und wann sie abgeschlossen ist.

  • Wir möchten den Fortschritt live in der Benutzeroberfläche anzeigen.

  • Anstatt nur die Textantwort zu erfassen, können wir den Toolnamen, die event.name, anzeigen, wenn die AgentEvent eine FunctionCall oder eine FunctionResponse ausgibt. Wenn sie fertig ist, können wir wie zuvor die TextMessage zurückgeben.

Multi-Turn-Unterhaltungssitzungen verwalten

  • Super!

  • Wir nennen diesen Aufruf „async run single-turn async“, was natürlich impliziert, dass es auch eine andere Art von API gibt, oder?

  • Ja.

  • Natürlich reicht es nicht aus, nur einen einzigen Schritt auszuführen.

  • Wir möchten mehrstufige Anfragen bearbeiten.

  • Als Nächstes sehen wir uns Multi-Turn-Sitzungen an. Ich denke, wir können zu meiner Demo zurückkehren und es hier noch etwas schwieriger machen.

  • Wir haben darüber gesprochen, dass wir einen Museumsbesuch haben, aber auch die Golden Gate Bay Cruise.

  • Dafür möchte ich erst einmal herausfinden, ob ich genug Budget habe, bevor ich es tatsächlich buche.

  • Stellen wir also eine Frage.

  • Mal sehen.

  • Ich möchte morgen mit meinem Team die Golden Gate Bay Cruise machen und dachte, dass das etwa 180 $ kostet.

  • Wenn ich das tue, wie hoch wäre dann mein verbleibendes Budget?

  • Was macht es also?

  • Sie beantwortet die Frage, wie hoch mein verbleibendes Budget sein wird.

  • Sie kennt also den aktuellen Reiseplan.

  • Es hat diese Informationen und kann sie weitergeben.

  • Ich kann hier aber auch eine Folgefrage stellen.

  • Okay, das ist gut.

  • Kannst du das meinen Ausgaben hinzufügen?

  • So, das war‘s auch schon.

  • Oh, es muss eine Währung sein, also cool, Demo-Effekt.

  • Okay, die Währung ist US-Dollar.

  • 180 $.

  • Na also.

  • Das ist das erste Mal, dass das passiert ist. Cool.

  • Okay, was passiert hier?

  • Es wird angezeigt, dass drei Versuche unternommen wurden, was die Kosten erhöht hat. Außerdem kann ich diese Sitzung aktualisieren.

  • Oben gibt es eine Schaltfläche zum Aktualisieren. Wenn ich darauf klicke, wird der gesamte Verlauf gelöscht und ich kann von vorn beginnen.

  • Damit können wir zu unseren Folien zurückkehren.

  • Wir haben die funktionierenden Demos, die alle das tun, was wir wollen. Ich denke, wir können jetzt zum Code zurückkehren.

  • Ja.

  • Das ist wirklich klasse.

  • Jolanda hat nicht erwähnt, dass es sich um das Ticket für die Golden Gate Bay Cruise handelt. Sie hat nur gesagt, dass es sich um Dollar handelt.

  • Woher kennt das Modell also den gesamten Kontext?

  • Das unterscheidet sich etwas von den beiden vorherigen Code-Snippets.

    // 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()
  • Anstatt AgentRunner und runSingleTurnAsync zu verwenden, müssen wir also eine Sitzung erstellen.

  • Hier rufen wir runner.getOrCreateSession mit tripId auf.

  • Warum müssen wir eine Sitzung erstellen?

  • Weil die runSingleTurnAsync den Speicher zurücksetzt und somit der Kontext verloren geht.

  • Wir können keine Multi-Turn-Unterhaltung führen.

  • Wenn Sie also eine Multi-Turn-Unterhaltung führen möchten, verwenden wir die Sitzung, die Agentensitzung.

  • Anschließend übergeben wir die tripId.

  • Warum müssen wir das tun?

  • Sie haben die Jetpacker-App schon einmal gesehen. Sie verwaltet mehrere Reisen.

  • Es gibt eine ADS-Reise, eine Paris-Reise, vielleicht eine Tokio-Reise.

  • Wir möchten also den Kontext der Fahrt beibehalten.

  • Wir wollen uns nicht gegenseitig in die Quere kommen, weißt du?

  • Anstatt runSingleTurnAsync aufzurufen, verwenden wir runAsync.

  • Das ist also anders.

  • Dies wird also in der Sitzung ausgeführt und wir haben immer noch denselben Prompt.

  • Und dann können wir wie zuvor AgentEvent, FunctionCall, FunctionResponse und TextMessage ausgeben.

  • Und als Jolanda auf den Button für eine neue Unterhaltung geklickt hat, also den Aktualisierungsbutton, konnten wir session.delete nutzen.

  • Ja.

  • Okay.

  • All das haben wir mit den neuen agentischen APIs gemacht. Das ist fantastisch und wird für viele Ihrer Anwendungsfälle hervorragend funktionieren.

  • Natürlich möchten wir, dass Sie Dinge schnell und einfach erledigen können, aber wir möchten Ihnen auch die Möglichkeit geben, komplexere Fälle zu bearbeiten.

  • Wenn Sie also eine Low-Level-Version dieser Dinge erstellen möchten, können Sie weiterhin die Direct Access APIs verwenden.

  • Victoria, kannst du erklären, was das bedeutet und wann wir wieder auf diese direkte Zugriffsebene zurückgreifen würden?

Erweiterte agentische Workflows und Tool-Integration

  • Ja, gerne, Jolanda.

  • Diesen direkten Zugriff kennen Sie vielleicht schon von der Prompt API.

  • Wir haben hier auch Funktionsaufrufe unterstützt und haben zwei Anwendungsfälle, in denen wir diese API anstelle des einfacheren agentischen Ablaufs bevorzugen würden.

  • Der erste ist ein sehr einfacher Anwendungsfall.

  • Im agentischen Ablauf senden wir die gesamte Unterhaltung, die Anfrage, die Tools, den Toolaufruf, die Toolantwort – also alles – zurück an das Modell.

  • Und vielleicht gibt es einige Anwendungsfälle, in denen wir das nicht tun möchten, sondern nur den Prompt senden wollen.

  • Angenommen, wir möchten einen Prompt an das Modell senden, der mit einem Toolaufruf zurückgegeben wird, und wir möchten den Toolaufruf nicht zurückgeben, sondern nur einen weiteren Prompt an das Modell senden. Wir können den Verlauf also bearbeiten, da wir die volle Kontrolle über den Verlauf haben.

  • Hier ist ein konkreteres Beispiel.

    // 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()
    )
  • Angenommen, wir haben eine App, mit der sich das Hintergrunddesign eines Geräts ändern lässt.

  • Angenommen, der Nutzer sendet die Anfrage „Ändere den Hintergrund in einen Sonnenuntergang“.

  • Und dann hat unsere App diese Funktion ausgeführt und der Hintergrund wurde in einen Sonnenuntergang geändert.

  • Und dann hat der Nutzer seine Meinung geändert und gesagt: „Oh, nein, ich meinte einen Strand.“

  • In diesem Fall müssen wir nicht den vollständigen Status senden, dass das Gerät den Hintergrund bereits in einen Sonnenuntergang geändert hat und ihn jetzt in einen Strand ändern möchte.

  • Nein, das ist nicht nötig.

  • Wir müssen nur den ersten und den zweiten Prompt senden, da der erste Prompt die Informationen zu einem Hintergrund enthält.

  • So weiß es, dass der Hintergrund in einen Strand geändert werden soll.

  • Dazu können wir die Prompt API verwenden.

  • Wir haben die volle Kontrolle über den Verlauf und speichern Kontext-Tokens auf dem Gerät, da das Token-Fenster auf dem Gerät begrenzter ist. So können wir mehr Tokens speichern.

  • Der zweite Anwendungsfall ist das genaue Gegenteil.

  • Wenn wir komplexe Anwendungsfälle haben, die die agentische API noch nicht unterstützt, z. B. Multi-Agenten-Systeme, bei denen mehrere Sub-Agenten miteinander interagieren, um komplexe Aufgaben zu lösen, oder ein Hybridsystem, bei dem wir sowohl On-Device-Modelle als auch Cloud-Modelle haben möchten, oder wenn wir benutzerdefinierten Speicher und Kontextkomprimierung haben möchten.

  • Das bezieht sich auf den Verlauf, also die vollständige Kontrolle über den Verlauf, die ich zuvor erwähnt habe.

  • Da das Kontextfenster begrenzt ist, möchten wir vielleicht nicht alles an das Modell zurücksenden, sondern nur den wichtigsten oder neuesten Teil zusammenfassen. Wenn wir die volle Kontrolle darüber haben möchten, verwenden wir die Direct Access API.

  • Für diese Anwendungsfälle können Sie Frameworks wie das Agent Development Kit (ADK) oder Crew verwenden oder ein eigenes benutzerdefiniertes Orchestrierungs-Framework erstellen.

  • Okay.

  • Das war alles, was wir zur agentischen API hatten.

  • Es gibt jedoch noch eine Sache, die wir ansprechen möchten.

  • Wir haben die ganze Zeit über In-App-Agenten gesprochen, also über die Erstellung einer eigenen agentischen Umgebung in Ihrer eigenen App. Wir haben aber auch darüber gesprochen, wie wir AppFunctions verwenden. Warum sollten Sie AppFunctions verwenden, wenn Sie nur in Ihrer eigenen App arbeiten?

  • Wir wissen, dass Entwickler wie Sie Dinge nicht gern zweimal machen, oder?

  • Wir kopieren und fügen Code nicht gern ein und wir duplizieren Geschäftslogik nicht gern.

  • Mit AppFunctions können wir unsere Tools mit einer sehr einfachen Änderung nicht nur für unseren In-App-Agenten, sondern auch für System-Agenten verfügbar machen.

  • Wie sieht das dann im Code aus?

  • Ja.

  • Mit AppFunction müssen wir sie nur einmal schreiben und können sie dann auf verschiedenen Ebenen verfügbar machen.

  • Sie erinnern sich, dass wir zuvor die ACCESS_LEVEL_SELF hatten, die die von uns geschriebenen Funktionen nur für unseren In-App-Agenten verfügbar machen. Wir können diese Zugriffsebene aber auch in ACCESS_LEVEL_ANDROID_TRUSTED ändern.

  • Dadurch werden die Funktionen vertrauenswürdigen Agenten im Android-Betriebssystem zugänglich gemacht.

  • Sie müssen also keine Funktionen, Befehle oder Datenbanklogik neu schreiben.

  • AppFunctions fungiert bereits als Stimme Ihrer App, sowohl intern als auch extern.

  • Wir wollen auch eine Demo davon, oder?

  • Okay, kehren wir zum Smartphone zurück und verlassen für unsere letzte Demo die Jetpacker-App.

  • Wir können also zu Gemini wechseln.

  • In Gemini können wir also @Jetpacker aufrufen.

  • Diese Funktion ist derzeit nur in einer eingeschränkten Betaversion verfügbar. Sie ist also nicht für alle sofort verfügbar, aber Sie können sie testen und ausprobieren.

  • Sehen wir uns Jetpacker an.

  • Was können wir tun?

  • Oh, ich habe heute Morgen einen Kaffee getrunken, oder?

  • Kannst du den Reisekosten für die ADS-Reise bitte einen Kaffee für 7 $ hinzufügen?

  • Na also.

  • Und wie Sie sehen, wird eine Verbindung zur Jetpacker-App hergestellt.

  • So, das war‘s auch schon.

  • Die Ausgabe wurde für mich hinzugefügt.

  • Wir haben also gesehen, wie wir alles in Jetpacker verwenden können.

  • Das Tolle am Jetpacker ist, dass wir auch den Flugmodus aktivieren können.

  • Wir haben es heute noch nicht gezeigt, aber wir können einfach den Flugmodus aktivieren und alles, was ihr in den Demos gesehen habt, funktioniert trotzdem. Ich möchte das kurz demonstrieren, damit ihr mir auch wirklich glaubt. Also schalten wir das WLAN aus.

  • Hat jemand ein gutes Beispiel für eine Ausgabe, die ich hinzufügen kann?

  • Mal sehen.

  • Vielleicht können wir „Was ist das?“ machen.

  • Oh, ein Mietwagen wäre gut, oder?

  • Okay.

  • Sagen Sie zum Beispiel: „Füge eine Ausgabe von 300 $ für den Mietwagen hinzu.“

  • Und schon haben wir es.

  • Okay.

  • Prima.

  • Okay, kehren wir zu den Folien zurück.

  • Wir haben über AppFunctions gesprochen.

  • Ich weiß, dass wir das Thema nur ganz kurz angeschnitten haben.

  • Wenn Sie mehr erfahren möchten, gibt es auch einen Vortrag von Dan und Sarah, in dem sie genauer darauf eingehen, wie Sie diese Funktion aktivieren und testen können, welche Tools wir haben usw.

  • Heute haben wir uns viele verschiedene Dinge angesehen.

  • Wir haben uns Single-Step, Multi-Step und Multi-Turn angesehen und schließlich auch, wie Sie die System-AppFunction verwenden können.

  • Bevor wir zum Ende kommen, haben wir noch ein paar Tipps für Sie.

Best Practices und Fazit

  • Ja.

  • Behalten Sie diese drei Tipps im Hinterkopf, wenn wir mit der Erstellung dieser agentischen Funktionen beginnen.

  • Der erste Punkt ist, dass die Toolsets fokussiert bleiben.

  • On-Device-Modelle haben aufgrund ihrer geringeren Größe nur eingeschränkte Funktionen.

  • Anstatt Dutzende von Tools zu registrieren, ist es also besser, eine kleinere Anzahl von Tools zu verwenden.

  • Und wie bereits erwähnt, sollten Sie klare KDoc-Beschreibungen schreiben, da das Modell diese verwendet, um zu entscheiden, was zurückgegeben werden soll.

  • Außerdem sollten wir eine maximale Anzahl von Turns festlegen.

  • Außerdem wird so verhindert, dass das Modell in eine Endlosschleife gerät, wenn es verwirrt ist.

  • Das bedeutet, dass wir den Akku des Nutzers möglicherweise entladen.

  • Wenn wir das also haben, können wir den Akku des Nutzers schonen.

  • Und schließlich müssen wir prüfen, ob Funktionsaufrufe auf dem Gerät bereits unterstützt werden, und einen geeigneten Fallback implementieren.

  • Wenn es beispielsweise nicht unterstützt wird, können Sie auf Cloud-Modelle oder Ihre Standard-UI verweisen.

  • Okay, ich denke, wir können das mit unseren Ressourcen abschließen.

  • Ich zeige Ihnen die QR-Codes noch einmal.

  • Bitte sehen Sie sich das an.

  • Ein wichtiger Punkt ist, dass wir dafür auch Android Skills haben.

  • Sie müssen also nicht jedes Detail genau kennen.

  • Wir haben eine vollständige Dokumentation und einen Android-Skill für diese neue agentische API.

  • Sehen Sie sich das also an.

  • Laden Sie die AICore-Entwicklervorschau herunter, um all diese Funktionen zu testen. Viel Spaß beim Programmieren!

Code

Code-Snippets

Kotlin-Snippet bei 09:53 in „Jetpacker-Agent-Demo und Funktionsaufrufe“
/**
 * 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...
}
Kotlin-Snippet bei 11:41 in „Building and implementing an AI agent runner“
val model = Generation.getClient()
val features = model.getAvailableFeatures()
if(FUNCTION_CALLING in features) {
  // Device is supported!
} else {
  // Graceful fallback
}
Kotlin-Snippet bei 12:07 in „Building and implementing an AI agent runner“
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 }

}
Kotlin-Snippet bei 17:23 in „Multi-step agent architecture and execution“
/**
* 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 { ... }
Kotlin-Snippet bei 18:23 in „Multi-step agent architecture and execution“
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
 }
}
Kotlin-Snippet bei 22:13 in „Managing multi-turn conversation sessions“
// 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()
Kotlin-Snippet bei 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()
)