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 getWeather i getTime 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();