Przewodnik po funkcji AI Edge Function na Androida

Pakiet AI Edge Function Calling SDK (FC SDK) to biblioteka, która umożliwia programistom korzystanie z wywoływania funkcji za pomocą modeli LLM na urządzeniu. Wywoływanie funkcji umożliwia łączenie modeli z narzędziami zewnętrznymi i interfejsami API, dzięki czemu modele mogą wywoływać określone funkcje z niezbędnymi parametrami do wykonywania działań w rzeczywistym świecie.

Zamiast tylko generować tekst, LLM korzystający z pakietu FC SDK może generować strukturalne wywołanie funkcji, która wykonuje działanie, np. wyszukuje aktualne informacje, ustawia alarmy lub dokonuje rezerwacji.

Ten przewodnik zawiera podstawowe informacje o tym, jak dodać interfejs LLM Inference API z pakietem FC SDK do aplikacji na Androida. W tym przewodniku skupimy się na dodawaniu funkcji wywoływania funkcji do modelu LLM na urządzeniu. Więcej informacji o korzystaniu z interfejsu LLM Inference API znajdziesz w przewodniku po interfejsie LLM Inference API na Androida.

Krótkie wprowadzenie

Aby użyć pakietu FC SDK w aplikacji na Androida, wykonaj te czynności. W tym przewodniku Szybki start używamy interfejsu LLM Inference API z modelem Hammer 2.1 (1,5 mld). Interfejs LLM Inference API jest zoptymalizowany pod kątem urządzeń z Androidem z wyższej półki, takich jak Pixel 8 i Samsung S23 lub nowsze, i nie obsługuje emulatorów urządzeń.

Dodawanie zależności

Pakiet SDK FC korzysta z biblioteki com.google.ai.edge.localagents:localagents-fc, a interfejs LLM Inference API 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 (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 model Hammer 1B w 8-bitowym formacie skwantyzowanym z Hugging Face. Więcej informacji o dostępnych modelach znajdziesz w dokumentacji dotyczącej 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, w tym krótkim wprowadzeniu znajdziesz 2 funkcje jako metody statyczne, które zwracają zakodowane na stałe odpowiedzi. Bardziej praktyczne wdrożenie polegałoby na zdefiniowaniu funkcji, które wywołują interfejs API REST lub pobierają informacje z bazy danych.

Funkcje getWeathergetTime są zdefiniowane w ten sposób:

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 FunctionDeclaration, aby opisać każdą funkcję, podając jej nazwę i opis oraz określając typy. 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 wnioskowania

Utwórz backend wnioskowania za pomocą interfejsu LLM Inference API i przekaż mu obiekt formatujący dla swojego modelu. Formatowanie SDK FC (ModelFormatter) działa jako formater i parser. W tym krótkim wprowadzeniu używamy modelu Gemma-3 1B, więc użyjemy tego kodu: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 sekcji opcji konfiguracji wnioskowania LLM.

Utwórz instancję modelu

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

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

Utwórz instancję modelu za pomocą GenerativeModel:

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

Rozpoczynanie sesji czatu

W tym krótkim przewodniku rozpoczniemy jedną sesję czatu. Możesz też utworzyć kilka niezależnych sesji.

Korzystając z nowej instancji GenerativeModel, rozpocznij sesję czatu:

var chat = generativeModel.startChat();

Wysyłaj do modelu prompty w sesji czatu za pomocą metody sendMessage:

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

Analizowanie odpowiedzi modelu

Po przekazaniu promptu do modelu aplikacja musi sprawdzić odpowiedź, aby określić, czy należy wywołać funkcję, czy wygenerować 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 to zbyt uproszczona implementacja. Więcej informacji o tym, jak aplikacja może analizować odpowiedzi modelu, znajdziesz w sekcji Formatowanie i parsowanie.

Jak to działa

W tej sekcji znajdziesz bardziej szczegółowe informacje o podstawowych pojęciach i komponentach pakietu SDK do wywoływania funkcji na Androidzie.

Modele

Pakiet SDK do wywoływania funkcji wymaga modelu z formaterem i parserem. Pakiet FC SDK zawiera wbudowany moduł formatujący i analizator składni dla tych modeli:

Aby używać innego modelu z pakietem FC SDK, musisz opracować własny formater i parser zgodny z interfejsem LLM Inference API.

Formatowanie i parsowanie

Kluczowym elementem obsługi wywoływania funkcji jest formatowanie promptów i parsowanie danych wyjściowych modelu. Są to 2 osobne procesy, ale pakiet FC SDK obsługuje zarówno formatowanie, jak i parsowanie za pomocą interfejsu ModelFormatter.

Moduł formatujący odpowiada za przekształcanie deklaracji funkcji strukturalnych w tekst, formatowanie odpowiedzi funkcji i wstawianie tokenów wskazujących początek i koniec tur rozmowy oraz role tych tur (np. „użytkownik”, „model”).

Parser odpowiada za wykrywanie, czy odpowiedź modelu zawiera wywołanie funkcji. Jeśli parser wykryje wywołanie funkcji, przeanalizuje je i przekształci w typ danych strukturalnych. 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ć ich zgodność ze wstępnie zdefiniowanym formatem strukturalnym, takim jak obiekty JSON lub wywołania funkcji w Pythonie. Wymuszając te ograniczenia, model formatuje swoje wyniki w sposób zgodny ze wstępnie zdefiniowanymi funkcjami i odpowiadającymi im typami parametrów.

Aby włączyć dekodowanie z ograniczeniami, 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.

Poniższy przykład pokazuje, jak skonfigurować dekodowanie z ograniczeniami, aby ograniczyć odpowiedź do wywołań narzędzi. Ogranicza wywołanie narzędzia do prefiksu ```tool_code\n i sufiksu \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();