Guía de llamadas a funciones de IA en Edge para Android

El SDK de llamadas a funciones de AI Edge (SDK de FC) es una biblioteca que permite a los desarrolladores usar llamadas a funciones con LLMs integrados en el dispositivo. La llamada a funciones te permite conectar modelos a herramientas y APIs externas, lo que permite que los modelos llamen a funciones específicas con los parámetros necesarios para ejecutar acciones en el mundo real.

En lugar de solo generar texto, un LLM que usa el SDK de FC puede generar una llamada estructurada a una función que ejecuta una acción, como buscar información actualizada, configurar alarmas o hacer reservaciones.

En esta guía, se explica cómo agregar la API de LLM Inference con el SDK de FC a una aplicación para Android en una guía de inicio rápido básica. En esta guía, se explica cómo agregar capacidades de llamada a funciones a un LLM integrado en el dispositivo. Para obtener más información sobre el uso de la API de LLM Inference, consulta la guía de LLM Inference para Android.

Guía de inicio rápido

Sigue estos pasos para usar el SDK de FC en tu aplicación para Android. En este inicio rápido, se usa la API de LLM Inference con Hammer 2.1 (1.5 mil millones). La API de LLM Inference está optimizada para dispositivos Android de alta gama, como el Pixel 8 y el Samsung S23 o modelos posteriores, y no admite de forma confiable los emuladores de dispositivos.

Cómo agregar dependencias

El SDK de FC usa la biblioteca com.google.ai.edge.localagents:localagents-fc, y la API de LLM Inference usa la biblioteca com.google.mediapipe:tasks-genai. Agrega ambas dependencias al archivo build.gradle de tu app para Android:

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

Para dispositivos con Android 12 (API 31) o versiones posteriores, agrega la dependencia de la biblioteca nativa de OpenCL. Para obtener más información, consulta la documentación sobre la etiqueta uses-native-library.

Agrega las siguientes etiquetas uses-native-library al archivo 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"/>

Descarga un modelo

Descarga Hammer 1B en un formato cuantizado de 8 bits desde Hugging Face. Para obtener más información sobre los modelos disponibles, consulta la documentación de Modelos.

Envía el contenido de la carpeta hammer2.1_1.5b_q8_ekv4096.task al 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

Cómo declarar definiciones de funciones

Define las funciones que estarán disponibles para el modelo. Para ilustrar el proceso, esta guía de inicio rápido incluye dos funciones como métodos estáticos que devuelven respuestas codificadas. Una implementación más práctica definiría funciones que llaman a una API de REST o recuperan información de una base de datos.

A continuación, se definen las funciones getWeather y 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() {}
}

Usa FunctionDeclaration para describir cada función, asignarle un nombre y una descripción, y especificar los tipos. Esto le informa al modelo qué hacen las funciones y cuándo llamarlas.

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();

Agrega las declaraciones de función a un objeto Tool:

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

Crea el backend de inferencia

Crea un backend de inferencia con la API de LLM Inference y pásale un objeto de formato para tu modelo. El formateador del SDK de FC (ModelFormatter) actúa como formateador y analizador. Como esta guía de inicio rápido usa 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 obtener más información, consulta las opciones de configuración de la inferencia del LLM.

Crea una instancia del modelo

Usa el objeto GenerativeModel para conectar el backend de inferencia, la instrucción del sistema y las herramientas. Ya tenemos el backend y las herramientas de inferencia, por lo que solo necesitamos crear la instrucción del sistema:

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

Crea una instancia del modelo con GenerativeModel:

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

Cómo iniciar una sesión de chat

Para simplificar, esta guía de inicio rápido inicia una sola sesión de chat. También puedes crear varias sesiones independientes.

Con la nueva instancia de GenerativeModel, inicia una sesión de chat:

var chat = generativeModel.startChat();

Envía instrucciones al modelo a través de la sesión de chat con el método sendMessage:

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

Analiza la respuesta del modelo

Después de pasar una instrucción al modelo, la aplicación debe examinar la respuesta para determinar si debe realizar una llamada a función o generar texto en lenguaje 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());
}

El código de muestra es una implementación demasiado simplificada. Para obtener más información sobre cómo una aplicación podría examinar las respuestas del modelo, consulta Formato y análisis.

Cómo funciona

En esta sección, se proporciona información más detallada sobre los conceptos y componentes principales del SDK de Function Calling para Android.

Modelos

El SDK de Function Calling requiere un modelo con un formateador y un analizador. El SDK de FC contiene un analizador y un formateador integrados para los siguientes modelos:

Para usar un modelo diferente con el SDK de FC, debes desarrollar tu propio formateador y analizador que sea compatible con la API de LLM Inference.

Formato y análisis

Una parte clave de la compatibilidad con las llamadas a funciones es el formato de las instrucciones y el análisis del resultado del modelo. Si bien estos son dos procesos separados, el SDK de FC controla el formato y el análisis con la interfaz ModelFormatter.

El formateador es responsable de convertir las declaraciones de funciones estructuradas en texto, dar formato a las respuestas de las funciones y, también, insertar tokens para indicar el inicio y el final de los turnos de conversación, así como los roles de esos turnos (p.ej., "usuario", "modelo").

El analizador es responsable de detectar si la respuesta del modelo contiene una llamada a función. Si el analizador detecta una llamada a una función, la analiza y la convierte en un tipo de datos estructurado. De lo contrario, trata el texto como una respuesta en lenguaje natural.

Decodificación restringida

La decodificación restringida es una técnica que guía la generación de resultados de los LLM para garantizar que se ajuste a un formato estructurado predefinido, como objetos JSON o llamadas a funciones de Python. Al aplicar estas restricciones, el modelo formatea sus resultados de una manera que se alinea con las funciones predefinidas y sus tipos de parámetros correspondientes.

Para habilitar la decodificación restringida, define las restricciones en un objeto ConstraintOptions y, luego, invoca el método enableConstraint de una instancia ChatSession. Cuando se habilita, esta restricción limitará la respuesta para que solo incluya las herramientas asociadas con GenerativeModel.

En el siguiente ejemplo, se muestra cómo configurar la decodificación restringida para limitar la respuesta a las llamadas a herramientas. Restringe la llamada a la herramienta para que comience con el prefijo ```tool_code\n y termine con el sufijo \n```.

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

Para inhabilitar la restricción activa dentro de la misma sesión, usa el método disableConstraint:

chatSession.disableConstraint();