Apps inteligentes

Desbloqueie recursos agênticos no seu app com a GenAI do Kit de ML

Alto-falantes

Sobre

Sobre a palestra

Leve os recursos de IA do Android para o próximo nível criando agentes autônomos no dispositivo. Nesta sessão, vamos falar sobre as novas APIs de agente do Kit de ML, criadas diretamente com base nas APIs de acesso direto ao modelo que você já conhece.

Usando o app de código aberto Jetpacker, você vai aprender a encadear ações de várias etapas em dados locais, gerenciar sessões multiturno com estado e contextos isolados e conectar o MCP do Android para potencializar assistentes no app e superfícies no nível do sistema Android com uma única base de código.

Resumo

  • O Gemini Nano 4 oferece suporte nativo à chamada de função, permitindo que os modelos no dispositivo traduzam linguagem natural em ações e acionem APIs locais.
  • A biblioteca AppFunctions do Jetpack define ferramentas usando descrições em linguagem natural do KDoc. Mudar os níveis de acesso pode expor essas funções a agentes do sistema, como o Gemini.
  • A nova biblioteca de fluxos de agentes (Alfa) na IA generativa do Kit de ML simplifica a criação de agentes ao processar o loop de execução e as chamadas de ferramentas.
  • AgentRunner coordena a execução de ferramentas e fornece métodos de sessão para conversas multiturno sem perder o contexto.
  • O acesso direto pela API Prompt continua disponível para controle de histórico simples ou configurações complexas, como sistemas híbridos e multiagentes.

Habilidades

Recursos

Gemini Notebook

Tópicos

  • ml-kit
  • on-device-ai
  • gemini-nano
  • ai-agents
  • chamada de função
  • app-functions
  • aicore

Transcrição

Introdução à IA no dispositivo e ao Gemini Nano

  • Meu nome é Jolanda Verhoef.

  • Sou engenheiro de relações com desenvolvedores no Android e, hoje, estou com Victoria Lestari, que faz parte da equipe de engenharia do AICore.

  • Olá!

  • Agradecemos a presença de vocês hoje de manhã.

  • Por isso, nossa equipe tem colaborado de perto há anos para tirar os modelos de IA de última geração da nuvem e executá-los diretamente em dispositivos Android.

  • Esses esforços estão agrupados no pacote de IA generativa do Kit de ML.

  • Por um lado, nesse pacote de IA generativa do Kit de ML, temos APIs de alto nível para soluções prontas, como resumo de texto, descrição de imagens e reconhecimento de fala.

  • Por outro lado, também abrimos o acesso direto ao modelo no dispositivo, o Gemini Nano.

  • Hoje, vamos dar um passo além.

  • Com esse acesso direto, você poderá criar agentes no app que podem raciocinar em várias interações, salvar o contexto da conversa e realizar ações no app.

  • O que isso significa, Victoria?

  • Sim.

  • Até agora, quando falamos sobre casos de uso no dispositivo, estamos pensando em tarefas mais curtas, simples e isoladas, como detectar um possível golpe ou resumir uma ligação.

  • E quando queremos criar algo mais complexo, como fazer perguntas sobre seu catálogo de fotos ou ao Google Maps, naturalmente recorremos a modelos de nuvem grandes.

  • Os modelos de nuvem oferecem a vantagem de um processamento imenso e dados em tempo real, mas a IA no dispositivo abre novas possibilidades de baixa latência e processamento de dados local e em tempo real.

  • O Gemini Nano já ampliou as possibilidades do que você pode fazer nos seus dispositivos.

  • Um exemplo é o app Capturas de Tela do Pixel, que permite pesquisar capturas de tela locais e fazer perguntas sobre elas.

  • Portanto, quando executamos agentes no dispositivo, isso significa que precisamos de um grande salto na capacidade do modelo, e é por isso que estamos criando o Gemini Nano 4, que é capaz de raciocinar em várias etapas e realizar ações reais.

  • Mas essa nova capacidade só importa se os desenvolvedores tiverem dispositivos reais para segmentar.

  • A boa notícia é que o Gemini Nano 4 já está disponível em alguns dispositivos de ponta, e você também pode testá-lo em muitos outros usando a prévia para desenvolvedores do AICore.

  • E estamos no caminho certo para alcançar 100 milhões de dispositivos no início do próximo ano.

  • Então, o que isso significa?

  • Isso significa que você pode começar a criar e testar essas experiências hoje, sabendo que o que você está criando hoje será dimensionado para um público enorme.

  • Já falamos sobre o Gemini Nano 4 e o alcance potencial. Agora, vamos falar sobre o que as habilidades agênticas realmente significam.

  • Assim, a IA agêntica pode planejar tarefas complexas, dividi-las em várias etapas e executá-las no seu app.

  • Digamos que o modelo Gemini Nano no dispositivo possa consultar seu banco de dados local, recuperar dados do back-end, raciocinar e responder às suas perguntas complexas e também acionar o fluxo de trabalho do app nativo no dispositivo.

  • Mas como uma IA realmente passa de um plano para ações reais?

  • E esse é o tema de hoje: chamada de função.

  • É o mecanismo principal dos recursos agênticos.

  • Ele traduz a linguagem natural em execução do sistema e faz com que o modelo passe de raciocínios passivos para a execução ativa, selecionando e acionando as APIs locais necessárias para resolver as tarefas complexas.

  • Também temos o prazer de anunciar que a chamada de função é compatível de forma nativa com o Gemini Nano 4.

  • No momento, ele está disponível na prévia para desenvolvedores do AICore e em breve será lançado para todos os dispositivos compatíveis com o Nano 4.

  • É ótimo que o modelo tenha esses recursos, mas eles ainda não oferecem as APIs para você, como desenvolvedor.

  • Por isso, também estamos trabalhando para adicionar as APIs certas ao pacote de IA generativa do Kit de ML para garantir que a criação de um agente seja muito fácil.

  • Então, começamos com as APIs de acesso direto que já tínhamos e adicionamos tipos de função e de histórico de conversas.

  • E com essas duas novas adições à API Prompt do Kit de ML, agora você pode criar esses recursos agênticos de baixo nível no seu app.

  • No entanto, há muitas coisas que você precisa fazer por conta própria nesse momento.

  • Portanto, é preciso executar as funções manualmente, salvar o histórico de ligações e também fazer um loop em várias chamadas.

  • Portanto, se houver várias chamadas que você precisa fazer, terá que criar algum tipo de sistema de loop por conta própria e criar suas próprias proteções para o sistema.

  • Isso é possível, mas queremos que seja rápido e fácil.

  • Além dessa biblioteca de API Prompt, também adicionamos uma nova biblioteca de fluxos agênticos.

  • Ele pode ser encontrado com com.mlkit.genai:agent-api e está disponível em Alfa.

  • Agora, com essa nova API, fazemos grande parte do trabalho pesado para você.

  • Assim, em vez de realizar várias etapas manuais, você pode criar um agente com apenas algumas linhas de código.

  • E é exatamente isso que vamos fazer hoje.

Demonstração do agente Jetpacker e chamada de função

  • Vamos ver como essas APIs funcionam com nosso app de exemplo, o Jetpacker.

  • Você já viu o Jetpacker no Google I/O e durante esta conferência.

  • Mas o que você pode fazer é ler este QR code e, se tiver interesse, dar uma olhada no código, apontar seu agente para ele, interagir com ele e saber mais sobre como implementamos todos esses recursos.

  • O Jetpacker é um assistente de viagem moderno que criamos para mostrar vários recursos inteligentes.

  • Ele já usa modelos no dispositivo e na nuvem para todos os tipos de APIs Prompt, mas agora vamos adicionar um agente a ele.

  • Para o Jetpacker, vamos usar a nova API de agente de IA generativa do Kit de ML que acabamos de mencionar. Ela vai usar o sistema subjacente do AI Core que é executado em dispositivos Android para se comunicar com o modelo Gemini Nano.

  • Agora, posso falar o quanto quiser, e vamos falar bastante, mas, claro, queremos mostrar isso.

  • Vamos mudar para o smartphone e mostrar isso em uma demonstração.

  • Estou com um Pixel 11 Pro aqui.

  • Acho que podemos mudar para o Pro, e você vai conseguir ver.

  • Pronto, aí está.

  • Como você pode ver, o app Jetpacker está em execução aqui.

  • Ele mostra várias viagens futuras, e posso ver a atual, que é em 29 de outubro. Temos o workshop de IA no dispositivo, a demonstração e, amanhã, um passeio de barco na Baía de Golden Gate e uma visita a um museu.

  • Tudo isso é ótimo.

  • Agora, podemos acessar a tela de despesas, onde estamos acompanhando o orçamento dessa viagem.

  • E, como vocês podem ver no canto inferior direito, há um novo brilho agêntico, que é o agente no dispositivo que criamos.

  • Vamos começar com um comando bem simples.

  • Acho que podemos pedir para ele adicionar uma despesa específica.

  • Por exemplo: "Adicione uma despesa de US $45 para o táxi até Mountain View".

  • Vamos lá.

  • Ele está preparando a despesa e adicionou a despesa.

  • Ótimo.

  • Certo, a demonstração 1 funcionou.

  • Vamos voltar aos slides e ver como isso funcionou.

  • Primeiro, como você percebeu, eu estava usando minha voz, e essa voz também está usando o modelo Gemini Nano com a API de reconhecimento de fala para transcrever o que eu estava dizendo em texto.

  • Esse texto é ótimo e pode ser enviado ao modelo Gemini Nano.

  • Mas o que ele faria?

  • Por exemplo, estou pedindo para adicionar algo às despesas e mencionando US $45, mas ele não sabe o que fazer, certo?

  • Nesse ponto, ele só vai dizer: "Não sei do que você está falando".

  • Então, o que queremos fazer é adicionar um pouco mais a esse comando.

  • Podemos adicionar ferramentas ao comando, então podemos adicionar todos os tipos de ferramentas. Nesse caso, estou adicionando o orçamento da viagem, o itinerário etc. Se você transmitir isso ao modelo Nano com nossas APIs, ele vai entender automaticamente que, em vez de retornar texto, agora ele pode retornar uma chamada de função.

  • No nosso caso, ele vai dizer: "Em vez de responder com texto, vou adicionar uma despesa de US $45 para o táxi até Mountain View".

  • Isso é ótimo, mas, Victoria, como isso funciona na prática?

  • Como criamos isso?

  • Sim.

  • Como definimos as ferramentas no código?

  • Então, é isso que vou explicar.

  • Por isso, com o MCP do Android, usamos a biblioteca AppFunctions do Jetpack para definir e publicar essas ferramentas.

  • Assim, a biblioteca AppFunctions pode expor os serviços, dados e ações do seu app no registro do SO Android para que possamos reutilizar as mesmas funções para declarar e descrever as ferramentas e usá-las no seu app.

  • Isso também permite que o usuário conclua essa tarefa por meio de agentes e interações no nível do sistema.

  • Vou dar um exemplo concreto no código.

    /**
     * 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...
    }
  • Este é um exemplo de AppFunctionDeclaration.

  • O comentário do KDoc na parte de cima descreve o que a função faz e os parâmetros dela, tudo em linguagem natural.

  • Portanto, a prática recomendada é fornecer a descrição mais clara possível, porque vamos enviá-la ao modelo.

  • Abaixo, há uma anotação, o @AppFunctionAccessLevel.

  • Isso define o escopo da visibilidade da função.

  • Neste exemplo, temos o ACCESS_LEVEL_SELF, o que significa que as funções que estamos escrevendo só ficam visíveis para o app, os agentes do app.

  • Depois disso, podemos definir e implementar a função de suspensão em si, além de escrever quantas funções de app forem necessárias e agrupá-las em uma única classe de serviço.

Como criar e implementar um executor de agentes de IA

  • Ok.

  • Então, temos as ferramentas e podemos escrevê-las, o que é ótimo.

  • Mas, para criar um agente, precisamos de um pouco mais do que apenas essas AppFunctions.

  • Então, temos mais três partes importantes para conhecer ao escrever um agente.

  • Primeiro, estamos definindo o agente.

  • Então, estamos definindo as instruções do sistema, as proteções, a configuração, como trabalhar com ele.

  • Também temos um AgentRunner.

  • O AgentRunner é o que executa o agente para nós.

  • Isso cuida do mecanismo de repetição que mencionamos.

  • Por fim, temos os AgentEvents, que são uma forma de o sistema, ou seja, a API, nos informar o que está fazendo.

  • Assim, ele vai informar quando estiver retornando texto, quando estiver fazendo uma chamada de ferramenta etc., para que possamos exibir isso na nossa interface.

  • Ok.

  • Vamos analisar o código real.

  • Ok.

  • Antes de escrever o código, precisamos verificar se a chamada de função está disponível no seu dispositivo, porque ela só é compatível com o Gemini Nano 4.

    val model = Generation.getClient()
    val features = model.getAvailableFeatures()
    if(FUNCTION_CALLING in features) {
      // Device is supported!
    } else {
      // Graceful fallback
    }
  • Primeiro, precisamos verificar chamando o modelo generativo getAvailableFeatures.

  • Basta verificar se a chamada de função está listada nos recursos.

  • Se não estiver, podemos implementar um substituto adequado.

  • Ok.

  • Agora, vamos ao código.

    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 }
    
    }
  • Sabemos que a chamada de função é compatível com o dispositivo, então podemos simplesmente começar a criar o agente usando o Agent.builder.

  • Depois, podemos definir o nome dele.

  • Podemos setInstructions para definir o contexto e a persona.

  • Por fim, chamamos enableAppFunctions.

  • Então, essa função vai chamar, vai fazer um loop e descobrir automaticamente todas as AppFunctions que listamos antes, e acabamos de criar o agente.

  • Em seguida, precisamos inicializar o AgentRunner.

  • Então, esse executor de agente vai gerenciar o loop de execução para nós.

  • Basta chamar um construtor e transmitir o agente para ele.

  • Por fim, podemos colocar o comando de voz transcrito em um bloco de conteúdo para criar o comando.

  • Em seguida, chamamos o executor.

  • Chamamos essa função runSingleTurnAsync no comando, e isso vai iniciar o agente, executar a inferência e fazer toda a inferência de IA.

  • E se quisermos apenas transmitir a resposta de texto final do modelo para nossa interface, basta chamar textTokens, que vai coletar a resposta conforme ela chega.

  • É isso.

  • São mesmo poucas linhas de código, então não estávamos brincando quando dissemos isso.

  • Com apenas algumas linhas de código, você tem um agente.

  • Esse agente vai fazer o que você quer.

  • Ele vai dar uma resposta em texto.

  • Mas, claro, nessa primeira demonstração, estávamos apenas executando um comando muito simples, certo?

  • Eu dei um comando, ele realizou uma ação e pronto. Então, ainda não é um agente completo, certo?

  • Então, vamos dar uma apimentada nas coisas.

  • Na próxima parte, quero dar um comando, mas quero dificultar um pouco para o agente.

Arquitetura e execução de agentes de várias etapas

  • Vamos voltar ao meu dispositivo de demonstração e ver se podemos tornar as coisas um pouco mais difíceis.

  • Já estamos lá.

  • Ótimo.

  • Como eu disse, temos uma visita ao museu planejada para amanhã.

  • E para essa visita ao museu, se eu abrir, você pode ver que o preço da entrada é US $25.

  • Agora, vamos ver se o agente é capaz de descobrir isso sozinho.

  • Então, vou voltar para a tela de despesas e dar um comando: "Adicione meu ingresso do museu às minhas despesas".

  • Vamos lá.

  • E agora, como você pode ver, ele está verificando meu itinerário, encontrando a visita ao museu e adicionando-a às minhas despesas.

  • Aqui, ele está seguindo três etapas para alcançar o objetivo e também me deu uma resposta final em texto.

  • Tudo isso funciona automaticamente.

  • Você viu a velocidade real desse modelo no dispositivo.

  • Vamos voltar aos slides e ver como isso funciona.

  • Como você pode ver, mudamos um pouco as coisas em comparação com nossa primeira demonstração.

  • Nesse caso, como você pode ver, eu não estava falando sobre o museu nem sobre a despesa. Então, quando o modelo recebeu o comando e as ferramentas, ele tentou decidir o que fazer primeiro.

  • Então, aqui, ele decide que precisa saber mais sobre meu itinerário atual, então ele vai chamar getItinerary.

  • Ele recebe uma resposta e sabe que há uma visita ao museu nesse itinerário, que provavelmente é o que ela estava mencionando.

  • Vamos encontrar os detalhes desse evento específico.

  • E, entre todos os outros metadados desse evento específico, ele vai ver que o valor é US $25.

  • Ele entende, volta e percebe que precisa chamar a função de adicionar uma despesa.

  • Aqui, vemos várias etapas acontecendo uma após a outra.

  • Victoria, você pode falar um pouco mais sobre isso?

  • Sim.

  • O que Jolanda acabou de descrever é um exemplo concreto da demonstração que ela acabou de mostrar, mas aqui, vou levar isso para uma camada de abstração um pouco mais conceitual.

  • Isso vai acontecer sempre que fizermos uma chamada de função em várias etapas.

  • Primeiro, este é o nosso app, e esse é o modelo Gemini Nano.

  • O usuário começa enviando a consulta e a lista de ferramentas disponíveis para o modelo.

  • Assim, o modelo vai usar essas informações para decidir o que enviar de volta.

  • No primeiro caso, ele envia de volta uma chamada de ferramenta, que vamos chamar de Chamada de Ferramenta 1.

  • Em seguida, o app vai executar essa chamada de ferramenta e receber uma resposta.

  • Em seguida, o que ele vai enviar de volta ao modelo é o histórico completo da conversa, que inclui a consulta original, a lista de ferramentas, a primeira chamada de ferramenta e a primeira resposta de ferramenta.

  • Em seguida, o modelo usa essas informações para decidir o que fazer em seguida.

  • Então, podemos continuar ajustando até chegar ao resultado final.

  • Digamos que o modelo retorne a enésima chamada de ferramenta e o app retorne todo o histórico da conversa novamente.

  • E quando o modelo estiver satisfeito, ou seja, souber que a consulta foi atendida, ele vai enviar uma resposta final de volta ao app, que poderá mostrar na interface que o processo foi concluído.

  • Ok.

  • Agora, vou explicar como implementar isso no código.

    /**
    * 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 { ... }
  • Você se lembra do exemplo do código de etapa única, e este é o código de várias etapas. Eles são muito parecidos, e o mais importante é escrever o KDoc de forma muito, muito clara.

  • Neste exemplo, a função getItinerary diz explicitamente que retorna apenas um resumo de alto nível dos eventos.

  • Também mencionamos que precisamos chamar getEventDetails com um eventId se quisermos ter detalhes mais específicos da viagem.

  • E o getEventDetails também tem o comentário do KDoc, que diz que ele pode recuperar detalhes, como preços de ingressos e outras observações.

  • Assim, ao usar o comentário, ao descrever o comentário claramente, podemos orientar o modelo para que ele saiba como as ferramentas interagem entre si.

  • Ok.

  • É assim que criamos o agente para fazer todo esse loop, esse loop do agente.

    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
     }
    }
  • Ele é muito parecido com o exemplo de etapa única, com apenas duas adições.

  • Primeiro, precisamos definir o número máximo de turnos. Neste exemplo, definimos cinco.

  • Então, o que ele faz?

  • Isso impede que o modelo entre em um loop infinito.

  • Digamos que ele tenha um comando ambíguo ou que fique chamando uma ferramenta após a outra e depois volte para a primeira. Se definirmos isso, ele vai parar, não vai continuar em loop.

  • Em seguida, ainda chamamos a mesma função para executá-lo, runSingleTurnAsync.

  • Portanto, embora seja chamado de turno único, um turno significa uma consulta do usuário.

  • Então, o usuário final espera que eu envie uma consulta e você retorne uma resposta, sem se preocupar muito com os detalhes do que aconteceu nos bastidores, como quantas vezes o loop foi executado e assim por diante.

  • Portanto, podemos continuar usando a mesma função.

  • E, como a chamada de ferramenta em várias etapas leva um pouco mais de tempo, você também viu na demonstração que ela mostra qual função está sendo chamada, o que está sendo executado e se o processo foi concluído.

  • Por isso, queremos mostrar o progresso em tempo real na interface.

  • Então, em vez de apenas coletar a resposta de texto, podemos exibir o nome da ferramenta, o event.name, sempre que o AgentEvent emitir um FunctionCall ou um FunctionResponse. Quando terminar, podemos retornar o TextMessage como antes.

Gerenciar sessões de conversa multiturno

  • Incrível!

  • Estamos chamando essa execução de assíncrona de turno único, o que, claro, pressupõe que também há outro tipo de API, certo?

  • Sim.

  • Portanto, não basta apenas uma etapa.

  • Queremos fazer várias etapas.

  • Vamos falar sobre sessões multiturno. Acho que podemos voltar à minha demonstração e torná-la um pouco mais difícil.

  • Estávamos falando sobre a visita ao museu, mas também tínhamos o Golden Gate Bay Cruise.

  • Para essa, só quero saber se tenho orçamento suficiente antes de reservar.

  • Vamos fazer uma pergunta.

  • Vamos descobrir.

  • Estou pensando em levar minha equipe para o Golden Gate Bay Cruise amanhã e achei que custava cerca de US $180.

  • Se eu fizer isso, qual será meu orçamento restante?

  • Então, o que ele está fazendo?

  • Ele está respondendo qual será meu orçamento restante.

  • Ela entende o itinerário atual.

  • Ele tem essas informações e pode dizer isso.

  • Mas também posso fazer uma pergunta complementar aqui.

  • Certo, isso é bom.

  • Você pode adicionar isso às minhas despesas?

  • Pronto, agora é só seguir em frente.

  • Ah, precisa de moeda, então, legal, efeito de demonstração.

  • Certo, então está em dólares.

  • US$ 180.

  • Vamos lá.

  • É a primeira vez que isso acontece, que legal.

  • Certo, então o que ele está fazendo?

  • Ele está me dizendo que levou três turnos e adicionou às despesas, e o que ele também faz é me permitir atualizar esta sessão.

  • Tenho um botão de atualização na parte de cima. Se eu clicar nele, limpo todo o histórico e posso começar de novo.

  • Com isso, acho que podemos voltar aos slides.

  • Temos as demonstrações funcionais, todas fazem o que queremos, então acho que podemos voltar ao código.

  • Sim.

  • Isso é muito legal.

  • Observe que Jolanda não mencionou, apenas diz que é em dólares, ela não diz que é para o ingresso do Golden Gate Bay Cruise.

  • Como o modelo sabe todo o contexto?

  • Isso é um pouco diferente do que mostrei nos dois snippets de código anteriores.

    // 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()
  • Em vez de usar AgentRunner e runSingleTurnAsync, precisamos criar uma sessão.

  • Aqui, chamamos runner.getOrCreateSession com tripId.

  • Por que precisamos criar uma sessão?

  • Como o runSingleTurnAsync vai redefinir a memória, ele não terá contexto.

  • Não será possível ter uma conversa multiturno.

  • Portanto, quando você quiser ter uma conversa em várias rodadas, use a sessão do agente.

  • Em seguida, transmitimos o tripId.

  • Por que precisamos fazer isso?

  • Como você já viu, o app Jetpacker gerencia várias viagens.

  • Há uma viagem de ADS, uma viagem para Paris, talvez uma viagem para Tóquio.

  • Por isso, queremos manter o contexto dentro da viagem.

  • Não queremos atrapalhar um ao outro, sabe?

  • Em vez de chamar runSingleTurnAsync, temos runAsync.

  • Então, isso é diferente.

  • Isso é executado na sessão, e o comando continua o mesmo.

  • E ainda podemos emitir AgentEvent, FunctionCall, FunctionResponse e TextMessage como antes, é igual.

  • E quando Jolanda clicou no botão de nova conversa, o botão de atualização, podemos ter session.delete.

  • Sim.

  • Ok.

  • Fizemos tudo isso com as novas APIs de agentes, o que é incrível e vai funcionar muito bem para muitos dos seus casos de uso.

  • Mas, claro, sempre queremos facilitar as coisas, mas também queremos que os casos mais complexos sejam possíveis para você.

  • Por isso, queremos garantir que, se necessário, você ainda possa usar as APIs de acesso direto para criar uma versão de nível mais baixo desses recursos.

  • Victoria, você pode explicar o que isso significa e quando voltaríamos para esse nível de acesso direto?

Agentic Workflows avançados e integração de ferramentas

  • Sim, claro, Jolanda.

  • Esse acesso direto é feito pela API Prompt, que você já deve conhecer.

  • Por isso, também oferecemos suporte à chamada de função aqui e temos dois casos de uso em que preferimos usar essa API em vez do fluxo de agentes mais fácil.

  • O primeiro é um caso de uso simples, muito simples.

  • No fluxo de agentes, enviamos a conversa completa, a consulta, as ferramentas, a chamada de ferramenta, a resposta da ferramenta, tudo de volta para o modelo.

  • E talvez haja alguns casos de uso em que não queremos fazer isso, apenas enviar o comando, digamos.

  • Digamos que queremos enviar um comando para o modelo que retorna com uma chamada de ferramenta, mas não queremos retornar a chamada de ferramenta, apenas enviar outro comando para o modelo. Assim, podemos editar o histórico, como se tivéssemos controle total sobre ele.

  • Vou dar um exemplo mais 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()
    )
  • Digamos que temos um app que pode mudar o tema do plano de fundo do seu dispositivo.

  • Digamos que o usuário envie esta consulta: "Mude o plano de fundo para um pôr do sol".

  • E, sim, nosso app executou essa função, e o plano de fundo mudou para um pôr do sol.

  • Depois, o usuário mudou de ideia e disse: "Ah, não, eu quis dizer uma praia".

  • E o que acontece aqui é que não precisamos enviar o estado completo de que o dispositivo já mudou o plano de fundo para um pôr do sol e agora quer mudar para uma praia.

  • Não, não precisamos fazer isso.

  • Só precisamos enviar o primeiro e o segundo comando, porque o primeiro contém as informações de um plano de fundo.

  • Assim, ele sabe que o usuário quer mudar o plano de fundo para uma praia.

  • Podemos usar a API Prompt para fazer isso.

  • Temos controle total sobre o histórico, salvamos o token de contexto no dispositivo porque, como você sabe, o dispositivo tem uma janela de tokens mais limitada e, com isso, podemos salvar mais tokens.

  • E o segundo caso de uso é o extremo oposto.

  • Então, se tivermos casos de uso complexos que a API de agentes ainda não consegue processar, como sistemas multiagente em que vários subagentes interagem entre si para resolver uma tarefa complexa, ou um sistema híbrido em que temos modelos no dispositivo e também queremos ter modelos na nuvem, ou se quisermos ter memória personalizada e compactação de contexto.

  • Isso se relaciona ao histórico, como o controle total do histórico que mencionei antes.

  • Digamos que, como a janela de contexto é limitada, não queremos enviar tudo de volta para o modelo, mas apenas resumir a parte mais importante ou talvez a mais recente. Para ter controle total sobre isso, usamos a API de acesso direto.

  • Para esses casos de uso, podemos usar frameworks como o Kit de Desenvolvimento de Agente (ADK) ou o Crew, ou você também pode criar seu próprio framework de orquestração personalizado.

  • Ok.

  • Isso é tudo o que tínhamos sobre a API de agentes.

  • No entanto, há mais uma coisa que gostaríamos de mencionar.

  • Estamos falando sobre agentes no app o tempo todo, então você está criando sua própria experiência de agente no seu app, mas também falamos sobre como estamos usando as AppFunctions. Por que você usaria as AppFunctions se a única coisa que está fazendo é no seu próprio app?

  • Sabemos que desenvolvedores como você não gostam de fazer as coisas duas vezes, certo?

  • Não gostamos de copiar e colar código nem de duplicar a lógica de negócios.

  • Com as AppFunctions, em vez de permitir que apenas o agente no app as consuma, com uma mudança muito simples, podemos disponibilizar nossas ferramentas também para agentes do sistema.

  • Como isso fica no código?

  • Sim.

  • Com as AppFunctions, só precisamos escrever uma vez e, em seguida, podemos expor as funções a diferentes níveis de camadas, conforme desejado.

  • Antes, tínhamos o ACCESS_LEVEL_SELF, que expõe as funções que escrevemos apenas para o agente no app, mas também podemos mudar esse nível de acesso para ACCESS_LEVEL_ANDROID_TRUSTED.

  • Isso vai expor as funções para agentes confiáveis no SO Android.

  • Portanto, não é necessário reescrever as funções, os comandos ou a lógica do banco de dados.

  • As AppFunctions já funcionam como a voz do seu app, tanto interna quanto externamente.

  • Também queremos uma demonstração disso, certo?

  • Vamos voltar ao smartphone e, para nossa demonstração final, vamos sair do app Jetpacker.

  • Então, podemos sair e entrar no Gemini.

  • No Gemini, podemos chamar @Jetpacker.

  • No momento, esse recurso está disponível apenas como uma versão Beta limitada, então nem todo mundo vai ter acesso a ele hoje, mas é possível testar e experimentar.

  • Vamos ver o que acontece com o Jetpacker.

  • O que devemos fazer?

  • Ah, tomei um café esta manhã, certo?

  • Você pode adicionar um café de US $7 às despesas da viagem para o ADS, por favor?

  • Vamos lá.

  • E o que você pode ver é que ele se conecta ao app Jetpacker.

  • Pronto.

  • Ele adicionou a despesa para mim.

  • Basicamente, vimos como usar tudo no Jetpacker.

  • O legal do Jetpacker é que também podemos ativar o modo avião.

  • Não mostramos isso hoje, mas podemos ativar o modo avião, e tudo o que vocês viram nas demonstrações, e eu quero mostrar isso, porque quero que vocês confiem em mim, então vamos desligar o Wi-Fi.

  • Alguém tem um bom exemplo de demonstração, uma despesa que eu possa adicionar?

  • Vamos descobrir.

  • Talvez possamos fazer "O que é?"

  • Ah, o aluguel de carros seria bom, né?

  • Ok.

  • Vamos dizer: "Adicione uma despesa de US $300 para o aluguel do carro".

  • Pronto, ele entendeu.

  • Ok.

  • Ótimo.

  • Vamos voltar aos slides.

  • Estávamos falando sobre as AppFunctions.

  • Sei que falamos sobre isso de forma bem superficial.

  • Se quiser saber mais, há outra palestra de Dan e Sarah que detalha como ativar e testar esse recurso, quais ferramentas temos etc.

  • Hoje, abordamos muitos assuntos diferentes.

  • Vimos as etapas únicas, as etapas múltiplas, as conversas de vários turnos e, por fim, como usar a AppFunction do sistema.

  • Agora, acho que temos algumas dicas antes de encerrar.

Práticas recomendadas e conclusão

  • Sim.

  • Lembre-se dessas três dicas ao começar a criar essas experiências agênticas.

  • O primeiro é manter o foco dos conjuntos de ferramentas.

  • Por isso, os modelos no dispositivo têm capacidade limitada devido à escala menor.

  • Portanto, em vez de registrar dezenas de ferramentas, é melhor usar um número menor.

  • E, como eu disse antes, escreva descrições claras do KDoc, porque é isso que o modelo usa para decidir o que vai retornar.

  • E também devemos definir uma proteção máxima de turnos.

  • Isso também impede que o modelo entre em um loop infinito se ficar confuso.

  • Isso significa que podemos descarregar a bateria do usuário.

  • Assim, podemos economizar a bateria do usuário.

  • Por fim, precisamos verificar se a chamada de função já está disponível no dispositivo e implementar um substituto adequado.

  • Por exemplo, se não houver suporte, use os modelos de nuvem ou a interface padrão.

  • Certo, acho que podemos encerrar isso com nossos recursos.

  • Mais uma vez, vou mostrar os QR codes.

  • Confira.

  • Acho que uma das coisas que devemos lembrar é que também temos as habilidades do Android para isso.

  • Assim, você não precisa se aprofundar em cada detalhe.

  • Temos uma documentação completa e uma habilidade do Android para acompanhar essa nova API de agentes.

  • Confira.

  • Baixe a prévia para desenvolvedores do AICore para testar tudo isso e boa programação.

Código

Snippets de código

Snippet do Kotlin aos 09:53 em "Demonstração do agente Jetpacker e chamada de função"
/**
 * 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 do Kotlin aos 11:41 em "Como criar e implementar um executor de agente de IA"
val model = Generation.getClient()
val features = model.getAvailableFeatures()
if(FUNCTION_CALLING in features) {
  // Device is supported!
} else {
  // Graceful fallback
}
Snippet do Kotlin aos 12:07 em "Como criar e implementar um executor de agente de 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 }

}
Snippet do Kotlin aos 17:23 em "Arquitetura e execução de agentes de várias etapas"
/**
* 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 em Kotlin aos 18:23 em "Arquitetura e execução de agentes em várias etapas"
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 do Kotlin em 22:13 em "Como gerenciar sessões de conversas com várias interações"
// 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 do Kotlin aos 25:02 em "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()
)