Guia de chamada de função de IA Edge para Android

O SDK de chamada de função de borda da IA (SDK FC) é uma biblioteca que permite que os desenvolvedores usem a chamada de função com LLMs no dispositivo. A chamada de função permite conectar modelos a ferramentas e APIs externas, permitindo que os modelos chamem funções específicas com os parâmetros necessários para executar ações reais.

Em vez de gerar apenas texto, um LLM que usa o SDK do FC pode gerar uma chamada estruturada para uma função que executa uma ação, como pesquisar informações atualizadas, definir alarmes ou fazer reservas.

Este guia apresenta um guia de início rápido básico para adicionar a API de inferência de LLM com o SDK do FC a um aplicativo Android. Este guia se concentra em adicionar recursos de chamada de função a um LLM no dispositivo. Para mais informações sobre como usar a API LLM Inference, consulte o guia LLM Inference para Android.

Guia de início rápido

Siga as etapas abaixo para usar o SDK do FC no seu app Android. Este tutorial rápido usa a API LLM Inference com o Hammer 2.1 (1.5B). A API Inference do LLM é otimizada para dispositivos Android de última geração, como o Pixel 8 e o Samsung S23 ou mais recente, e não oferece suporte confiável a emuladores de dispositivos.

Adicionar dependências

O SDK FC usa a biblioteca com.google.ai.edge.localagents:localagents-fc, e a API de inferência do LLM usa a biblioteca com.google.mediapipe:tasks-genai. Adicione as duas dependências ao arquivo build.gradle do app Android:

dependencies {
    implementation 'com.google.mediapipe:tasks-genai:0.10.24'
    implementation 'com.google.ai.edge.localagents:localagents-fc:0.1.0'
}

Para dispositivos com o Android 12 (API 31) ou mais recente, adicione a dependência de biblioteca OpenCL nativa. Para mais informações, consulte a documentação da tag uses-native-library.

Adicione as seguintes tags uses-native-library ao arquivo AndroidManifest.xml:

<uses-native-library android:name="libOpenCL.so" android:required="false"/>
<uses-native-library android:name="libOpenCL-car.so" android:required="false"/>
<uses-native-library android:name="libOpenCL-pixel.so" android:required="false"/>

Fazer o download de um modelo

Faça o download do Hammer 1B em um formato quantizado de 8 bits no Hugging Face. Para mais informações sobre os modelos disponíveis, consulte a documentação de modelos.

Envie o conteúdo da pasta hammer2.1_1.5b_q8_ekv4096.task para o dispositivo Android.

$ adb shell rm -r /data/local/tmp/llm/ # Remove any previously loaded models
$ adb shell mkdir -p /data/local/tmp/llm/
$ adb push hammer2.1_1.5b_q8_ekv4096.task /data/local/tmp/llm/hammer2.1_1.5b_q8_ekv4096.task

Declarar definições de função

Defina as funções que serão disponibilizadas para o modelo. Para ilustrar o processo, este guia de início rápido inclui duas funções como métodos estáticos que retornam respostas codificadas em disco. Uma implementação mais prática definiria funções que chamam uma API REST ou recuperam informações de um banco de dados.

O código a seguir define as funções getWeather e getTime:

class ToolsForLlm {
    public static String getWeather(String location) {
        return "Cloudy, 56°F";
    }

    public static String getTime(String timezone) {
        return "7:00 PM " + timezone;
    }

    private ToolsForLlm() {}
}

Use FunctionDeclaration para descrever cada função, atribuindo um nome e uma descrição a cada uma e especificando os tipos. Isso informa ao modelo o que as funções fazem e quando fazer chamadas de função.

var getWeather = FunctionDeclaration.newBuilder()
    .setName("getWeather")
    .setDescription("Returns the weather conditions at a location.")
    .setParameters(
        Schema.newBuilder()
            .setType(Type.OBJECT)
            .putProperties(
                "location",
                Schema.newBuilder()
                    .setType(Type.STRING)
                    .setDescription("The location for the weather report.")
                    .build())
            .build())
    .build();
var getTime = FunctionDeclaration.newBuilder()
    .setName("getTime")
    .setDescription("Returns the current time in the given timezone.")

    .setParameters(
        Schema.newBuilder()
            .setType(Type.OBJECT)
            .putProperties(
                "timezone",
                Schema.newBuilder()
                    .setType(Type.STRING)
                    .setDescription("The timezone to get the time from.")
                    .build())
            .build())
    .build();

Adicione as declarações de função a um objeto Tool:

var tool = Tool.newBuilder()
    .addFunctionDeclarations(getWeather)
    .addFunctionDeclarations(getTime)
    .build();

Criar o back-end de inferência

Crie um back-end de inferência usando a API de inferência de LLM e transmita um objeto formatador para seu modelo. O formatador do SDK do FC (ModelFormatter) atua como um formatador e analisador. Como este guia de início rápido usa o Gemma-3 1B, usaremos GemmaFormatter:

var llmInferenceOptions = LlmInferenceOptions.builder()
    .setModelPath(modelFile.getAbsolutePath())
    .build();
var llmInference = LlmInference.createFromOptions(context, llmInferenceOptions);
var llmInferenceBackend = new llmInferenceBackend(llmInference, new GemmaFormatter());

Para mais informações, consulte as opções de configuração da inferência LLM.

Instanciar o modelo

Use o objeto GenerativeModel para conectar o back-end de inferência, o comando do sistema e as ferramentas. Já temos o back-end de inferência e as ferramentas. Portanto, só precisamos criar a solicitação do sistema:

var systemInstruction = Content.newBuilder()
      .setRole("system")
      .addParts(Part.newBuilder().setText("You are a helpful assistant."))
      .build();

Instancie o modelo com GenerativeModel:

var generativeModel = new GenerativeModel(
    llmInferenceBackend,
    systemInstruction,
    List.of(tool),
)

Iniciar uma sessão de chat

Para simplificar, este guia de início rápido inicia uma única sessão de chat. Também é possível criar várias sessões independentes.

Usando a nova instância de GenerativeModel, inicie uma sessão de chat:

var chat = generativeModel.startChat();

Envie comandos para o modelo pela sessão de chat usando o método sendMessage:

var response = chat.sendMessage("How's the weather in San Francisco?");

Analisar a resposta do modelo

Depois de transmitir um comando ao modelo, o aplicativo precisa examinar a resposta para determinar se vai fazer uma chamada de função ou gerar um texto de linguagem natural.

// Extract the model's message from the response.
var message = response.getCandidates(0).getContent().getParts(0);

// If the message contains a function call, execute the function.
if (message.hasFunctionCall()) {
  var functionCall = message.getFunctionCall();
  var args = functionCall.getArgs().getFieldsMap();
  var result = null;

  // Call the appropriate function.
  switch (functionCall.getName()) {
    case "getWeather":
      result = ToolsForLlm.getWeather(args.get("location").getStringValue());
      break;
    case "getTime":
      result = ToolsForLlm.getWeather(args.get("timezone").getStringValue());
      break;
    default:
      throw new Exception("Function does not exist:" + functionCall.getName());
  }
  // Return the result of the function call to the model.
  var functionResponse =
      FunctionResponse.newBuilder()
          .setName(functionCall.getName())
          .setResponse(
              Struct.newBuilder()
                  .putFields("result", Value.newBuilder().setStringValue(result).build()))
          .build();
  var functionResponseContent = Content.newBuilder()
        .setRole("user")
        .addParts(Part.newBuilder().setFunctionResponse(functionResponse))
        .build();
  var response = chat.sendMessage(functionResponseContent);
} else if (message.hasText()) {
  Log.i(message.getText());
}

O código de exemplo é uma implementação muito simplificada. Para mais informações sobre como um aplicativo pode examinar respostas de modelos, consulte Formatação e análise sintática.

Como funciona

Esta seção fornece informações mais detalhadas sobre os principais conceitos e componentes do SDK de chamadas de função para Android.

Modelos

O SDK de chamada de função requer um modelo com um formatador e um analisador. O SDK do FC contém um formatador e um analisador integrados para os seguintes modelos:

  • Gemma: use o GemmaFormatter.
  • Llama: use o LlamaFormatter.
  • Martelo: use o HammerFormatter.

Para usar um modelo diferente com o SDK do FC, você precisa desenvolver seu próprio formatador e analisador compatível com a API de inferência do LLM.

Formatação e análise

Uma parte importante do suporte a chamadas de função é a formatação de comandos e a análise da saída do modelo. Embora sejam dois processos separados, o SDK do FC processa a formatação e a análise com a interface ModelFormatter.

O formatador é responsável por converter as declarações de função estruturadas em texto, formatar respostas de função e inserir tokens para indicar o início e o fim das rodadas de conversa, bem como as funções dessas rodadas (por exemplo, "usuário", "modelo").

O analisador é responsável por detectar se a resposta do modelo contém uma chamada de função. Se o analisador detectar uma chamada de função, ele vai analisá-la em um tipo de dados estruturado. Caso contrário, ele vai tratar o texto como uma resposta de linguagem natural.

Decodificação restrita

A decodificação restrita é uma técnica que orienta a geração de saída de LLMs para garantir que ela adere a um formato estruturado predefinido, como objetos JSON ou chamadas de função do Python. Ao aplicar essas restrições, o modelo formata as saídas de modo que elas se alinhem às funções predefinidas e aos tipos de parâmetro correspondentes.

Para ativar a decodificação restrita, defina as restrições em um objeto ConstraintOptions e invoque o método enableConstraint de uma instância ChatSession. Quando ativada, essa restrição restringe a resposta para incluir apenas as ferramentas associadas ao GenerativeModel.

O exemplo a seguir demonstra como configurar a decodificação restrita para restringir a resposta a chamadas de ferramentas. Ele restringe a chamada de ferramenta para começar com o prefixo ```tool_code\n e terminar com o sufixo \n```.

ConstraintOptions constraintOptions = ConstraintOptions.newBuilder()
  .setToolCallOnly( ConstraintOptions.ToolCallOnly.newBuilder()
  .setConstraintPrefix("```tool_code\n")
  .setConstraintSuffix("\n```"))
  .build();
chatSession.enableConstraint(constraintOptions);

Para desativar a restrição ativa na mesma sessão, use o método disableConstraint:

chatSession.disableConstraint();