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

El SDK de llamadas a funciones de Vertex AI Edge (SDK de FC) es una biblioteca que permite a los desarrolladores usar llamadas a función con LLM en el dispositivo. Las llamadas a función te permiten 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 reales.

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 reservas.

En esta guía, se explica un instructivo básico para agregar la API de inferencia de LLM con el SDK de FC a una aplicación para Android. En esta guía, se enfoca en agregar capacidades de llamadas a función 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 guía de inicio rápido, se usa la API de inferencia de LLM con Hammer 2.1 (1.5B). La API de inferencia de LLM está optimizada para dispositivos Android de alta gama, como Pixel 8 y Samsung S23 o versiones posteriores, y no es compatible de forma confiable con 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 inferencia de LLM 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.23'
    implementation 'com.google.ai.edge.localagents:localagents-fc:0.1.0'
}

En el caso de los dispositivos con Android 12 (nivel de 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 Gemma-3 1B en un formato cuantificado de 4 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 gemma3-1b-it-int4.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 gemma3-1b-it-int4.task /data/local/tmp/llm/gemma3-1b-it-int4.task

Declara 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 muestran respuestas hard-coded. Una implementación más práctica definiría funciones que llamen a una API de REST o recuperen 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 realizar llamadas a función.

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 un formateador y un 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 inferencia de LLM.

Crea una instancia del modelo

Usa el objeto GenerativeModel para conectar el backend de inferencia, el mensaje del sistema y las herramientas. Ya tenemos el backend y las herramientas de inferencia, por lo que solo debemos 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 mostrar texto de 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 response = chat.sendMessage(functionResponse);
} 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 Formateo y análisis.

Cómo funciona

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

Modelos

El SDK de llamadas a funciones requiere un modelo con un formato 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 formato y analizador que sea compatible con la API de inferencia de LLM.

Formateo y análisis

Una parte clave de la compatibilidad con llamadas a función es el formato de las instrucciones y el análisis del resultado del modelo. Si bien estos son dos procesos independientes, 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, además, 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 función, la analiza en un tipo de datos estructurado. De lo contrario, trata el texto como una respuesta de 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 adhieran a un formato estructurado predefinido, como objetos JSON o llamadas a funciones de Python. Cuando se aplican estas restricciones, el modelo da formato a 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 de ChatSession. Cuando se habilita, esta restricción restringe 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 de herramientas. Restringe la llamada a la herramienta para que comience con el prefijo ```tool_code\n y finalice 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()