Przewodnik po funkcji AI Edge Function na Androida

Pakiet SDK wywoływania funkcji AI Edge (FC SDK) to biblioteka, która umożliwia deweloperom używanie wywoływania funkcji z usługami LLM na urządzeniu. Wywoływanie funkcji umożliwia łączenie modeli z zewnętrznymi narzędziami i interfejsami API, dzięki czemu modele mogą wywoływać określone funkcje z wymaganymi parametrami, aby wykonywać działania w rzeczywistych warunkach.

Zamiast generować tylko tekst, LLM korzystający z pakietu FC SDK może generować wywołanie funkcji, które wykonuje działanie, takie jak wyszukiwanie aktualnych informacji, ustawianie alarmów czy rezerwowanie.

W tym przewodniku znajdziesz podstawowe informacje o dodawaniu interfejsu LLM Inference API za pomocą pakietu FC SDK do aplikacji na Androida. Ten przewodnik skupia się na dodawaniu funkcji wywoływania funkcji do LLM na urządzeniu. Więcej informacji o korzystaniu z interfejsu LLM Inference API znajdziesz w przewodniku LLM Inference na Androida.

Krótkie wprowadzenie

Aby używać pakietu FC SDK w aplikacji na Androida, wykonaj te czynności. Ten samouczek używa interfejsu LLM Inference API z użyciem Hammer 2.1 (1,5 mld). Interfejs API LLM Inference jest zoptymalizowany pod kątem zaawansowanych urządzeń z Androidem, takich jak Pixel 8 i Samsung S23 lub nowszych, i nie obsługuje niezawodnie emulatorów urządzeń.

Dodawanie zależności

Pakiet FC SDK korzysta z biblioteki com.google.ai.edge.localagents:localagents-fc, a interfejs LLM Inference API korzysta z biblioteki com.google.mediapipe:tasks-genai. Dodaj obie zależności do pliku build.gradle aplikacji na Androida:

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

W przypadku urządzeń z Androidem 12 (poziom API 31) lub nowszym dodaj zależność od natywnej biblioteki OpenCL. Więcej informacji znajdziesz w dokumentacji dotyczącej tagu uses-native-library.

Dodaj do pliku AndroidManifest.xml te tagi uses-native-library:

<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"/>

Pobieranie modelu

Pobierz Hammer 1B w 8-bitowym formacie kwantowanym z HuggingFace. Więcej informacji o dostępnych modelach znajdziesz w dokumentacji modeli.

Przekaż zawartość folderu hammer2.1_1.5b_q8_ekv4096.task na urządzenie z Androidem.

$ 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

Deklarowanie definicji funkcji

Określ funkcje, które będą dostępne dla modelu. Aby zilustrować ten proces, to krótkie wprowadzenie zawiera 2 funkcje jako metody statyczne, które zwracają zakodowane odpowiedzi. Bardziej praktyczne wdrożenie polega na zdefiniowaniu funkcji, które wywołują interfejs API REST lub pobierają informacje z bazy danych.

Funkcję getWeathergetTime definiują te wyrażenia:

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() {}
}

Użyj funkcji FunctionDeclaration, aby opisać każdą funkcję, podając jej nazwę i opis oraz określając typ. Dzięki temu model wie, co robią funkcje i kiedy należy je wywoływać.

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

Dodaj deklaracje funkcji do obiektu Tool:

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

Tworzenie backendu do wnioskowania

Utwórz backend wnioskowania za pomocą interfejsu LLM Inference API i podaj mu obiekt formatowania dla modelu. Formater pakietu FC SDK (ModelFormatter) pełni funkcję formatera i parsowania. Ponieważ w tym krótkim wprowadzeniu używamy Gemma-3 1B, użyjemy: GemmaFormatter

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

Więcej informacji znajdziesz w opcjach konfiguracji wnioskowania LLM.

Tworzenie wystąpienia modelu

Użyj obiektu GenerativeModel, aby połączyć backend inferencji, prompt systemu i narzędzia. Mamy już narzędzia i system do obsługi wnioskowania, więc musimy tylko utworzyć prompt systemowy:

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

Tworzenie wystąpienia modelu za pomocą GenerativeModel:

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

Rozpoczęcie sesji czatu

W tym krótkim przewodniku rozpoczniesz jedną sesję czatu. Możesz też tworzyć wiele niezależnych sesji.

Używając nowej instancji GenerativeModel, rozpocznij sesję czatu:

var chat = generativeModel.startChat();

Wysyłanie promptów do modelu podczas sesji czatu za pomocą metody sendMessage:

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

Przeanalizuj odpowiedź modelu.

Po przekazaniu prompta do modelu aplikacja musi sprawdzić odpowiedź, aby określić, czy ma wywołać funkcję, czy wyświetlić tekst w języku naturalnym.

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

Przykładowy kod jest zbyt uproszczony. Więcej informacji o tym, jak aplikacja może sprawdzać odpowiedzi modelu, znajdziesz w artykule Formatowanie i analizowanie.

Jak to działa

W tej sekcji znajdziesz szczegółowe informacje o podstawowych pojęciach i elementach pakietu Function Calling SDK na Androida.

Modele

Pakiet SDK wywoływania funkcji wymaga modelu z formaterem i analizatorem. Pakiet FC SDK zawiera wbudowany formater i parsujący dla tych modeli:

  • Gemma użyj funkcji GemmaFormatter.
  • Llama: użyj LlamaFormatter.
  • Młot: użyj opcji HammerFormatter.

Aby używać innego modelu z pakietem FC SDK, musisz opracować własny formater i analizator, który będzie zgodny z interfejsem LLM Inference API.

Formatowanie i analizowanie

Kluczową częścią obsługi wywoływania funkcji jest formatowanie promptów i analizowanie danych wyjściowych modelu. Chociaż są to 2 oddzielne procesy, pakiet FC SDK obsługuje zarówno formatowanie, jak i analizowanie za pomocą interfejsu ModelFormatter.

Formatowanie odpowiada za konwertowanie deklaracji funkcji strukturalnych na tekst, formatowanie odpowiedzi funkcji oraz wstawianie tokenów wskazujących początek i koniec tur konwersacji, a także role tych tur (np. „user” i „model”).

Analizator odpowiada za wykrywanie, czy odpowiedź modelu zawiera wywołanie funkcji. Jeśli wykryje wywołanie funkcji, przeanalizuje je i przekształci w uporządkowany typ danych. W przeciwnym razie traktuje tekst jako odpowiedź w języku naturalnym.

Dekodowanie z ograniczeniami

Dekodowanie z ograniczeniami to technika, która kieruje generowaniem danych wyjściowych przez duże modele językowe, aby zapewnić zgodność z wstępnie zdefiniowanym formatem uporządkowanym, takim jak obiekty JSON czy wywołania funkcji Pythona. Dzięki stosowaniu tych ograniczeń model formatuje dane wyjściowe w sposób zgodny z wstępnie zdefiniowanymi funkcjami i odpowiednimi typami parametrów.

Aby włączyć kodowanie ograniczone, zdefiniuj ograniczenia w obiekcie ConstraintOptions i wywołaj metodę enableConstraint instancji ChatSession. Gdy to ograniczenie jest włączone, odpowiedź będzie zawierać tylko narzędzia powiązane z GenerativeModel.

W tym przykładzie pokazujemy, jak skonfigurować kodowanie ograniczone, aby ograniczyć odpowiedź do wywołań narzędzi. Wywołanie narzędzia musi zaczynać się od prefiksu ```tool_code\n i kończyć sufiksem \n```.

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

Aby wyłączyć aktywne ograniczenie w ramach tej samej sesji, użyj metody disableConstraint:

chatSession.disableConstraint();