Narzędzia umożliwiają agentowi na urządzeniu dostęp do danych aplikacji, komunikację z usługami backendu i wykonywanie działań w imieniu użytkownika.
Jak działają narzędzia
Gdy użytkownik prześle prompt, klasa AgentRunner wykonuje pętlę agenta obejmującą 4 etapy:
- Obserwacja: Gemini Nano analizuje prompt użytkownika i bieżący kontekst rozmowy pod kątem zarejestrowanych deklaracji narzędzi (przestrzeń działań agenta).
- Powód: model określa, czy narzędzie w aplikacji jest wymagane. Jeśli potrzebne jest narzędzie, Gemini Nano wysyła ustrukturyzowane wywołanie narzędzia zawierające nazwę funkcji i wpisane argumenty.
- Działanie: instancja
AgentRunnerautomatycznie przechwytuje to wywołanie i wykonuje odpowiednią funkcję Kotlin w aplikacji. - Ocena opinii: dane wyjściowe narzędzia są przekazywane z powrotem do Gemini Nano. Model ocenia opinię i odpowiada użytkownikowi lub wywołuje inne narzędzie, dopóki nie zostanie spełniony warunek zakończenia (osiągnięcie celu lub limitu
maxTurns).
Narzędzia deklaratywne z funkcjami aplikacji na Androida
Zalecanym sposobem definiowania narzędzi jest deklaratywne użycie funkcji aplikacji na Androida (androidx.appfunctions). Funkcje aplikacji zapewniają ustandaryzowany model platformy do deklarowania typowanych funkcji w aplikacji z automatycznym generowaniem schematu.
1. Dodawanie zależności Gradle
Dodaj artefakty AppFunctions obok genai-agent w pliku build.gradle.kts modułu:
dependencies {
implementation("com.google.mlkit:genai-agent:1.0.0-alpha1")
implementation("androidx.appfunctions:appfunctions:1.0.0-alpha13")
ksp("androidx.appfunctions:appfunctions-compiler:1.0.0-alpha13")
}
2. Dodaj do metod narzędzia adnotację @AppFunctionDeclaration.
Dodaj do metod narzędzia publicznego adnotację @AppFunctionDeclaration(isDescribedByKDoc = true). Opisy parametrów są dokumentowane w standardowych tagach KDoc @param.
Funkcje AppFunctions wymagają, aby zwracane wartości były typami podstawowymi (takimi jak String, Int lub Boolean) lub klasami danych strukturalnych z adnotacją @AppFunctionSerializable.
package com.example.ai.agent.tools
import androidx.appfunctions.AppFunctionDeclaration
import androidx.appfunctions.AppFunctionSerializable
import androidx.appfunctions.AppFunctionService
import androidx.appfunctions.AppFunctionServiceEntryPoint
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
/**
* Structured details for an order.
*/
@AppFunctionSerializable
data class OrderDetails(
val orderId: String,
val status: String,
val estimatedDelivery: String?,
val itemCount: Int,
)
/**
* Result returned when cancelling an order.
*/
@AppFunctionSerializable
data class CancelOrderResult(
val orderId: String,
val cancelled: Boolean,
val message: String,
)
@AppFunctionServiceEntryPoint(
serviceName = "OrderAppFunctionService",
appFunctionXmlFileName = "order_app_functions",
)
abstract class OrderTools : AppFunctionService() {
private val orderRepository = OrderRepository()
/**
* Fetches details and delivery status for a customer order by ID.
*
* @param orderId The alphanumeric order identifier (for example, ORD-8492)
*/
@AppFunctionDeclaration(isDescribedByKDoc = true)
suspend fun fetch_order_details(
orderId: String
): OrderDetails = withContext(Dispatchers.IO) {
val order = orderRepository.findOrder(orderId)
if (order != null) {
OrderDetails(
orderId = order.id,
status = order.status,
estimatedDelivery = order.deliveryEstimate,
itemCount = order.items.size,
)
} else {
OrderDetails(
orderId = orderId,
status = "NOT_FOUND",
estimatedDelivery = null,
itemCount = 0,
)
}
}
/**
* Requests a cancellation or return for an active order.
*
* @param orderId The order identifier to cancel
* @param reason The reason provided by the user for cancellation
*/
@AppFunctionDeclaration(isDescribedByKDoc = true)
suspend fun cancel_order(
orderId: String,
reason: String? = null
): CancelOrderResult = withContext(Dispatchers.IO) {
val success = orderRepository.cancelOrder(orderId, reason)
CancelOrderResult(
orderId = orderId,
cancelled = success,
message = if (success) {
"Order cancellation processed."
} else {
"Order cannot be cancelled at this stage."
}
)
}
}
Zarejestruj wygenerowaną usługę w pliku AndroidManifest.xml aplikacji:
<service
android:name=".OrderAppFunctionService"
android:permission="android.permission.BIND_APP_FUNCTION_SERVICE"
android:exported="true">
<property
android:name="android.app.appfunctions.schema"
android:value="app_functions_schema.xsd" />
<property
android:name="android.app.appfunctions.v2"
android:value="order_app_functions.xml" />
<intent-filter>
<action android:name="android.app.appfunctions.AppFunctionService" />
</intent-filter>
</service>
3. Ograniczanie dostępu elementu wywołującego za pomocą stałej ACCESS_LEVEL_SELF
Funkcje AppFunctions nie muszą być publiczne dla systemu ani asystentów zewnętrznych. W Androidzie kontrola dostępu jest zarządzana za pomocą poziomów dostępu elementu wywołującego, a nie ręcznych kontroli tożsamości elementu wywołującego.
Domyślnie (przy użyciu stałej ACCESS_LEVEL_ANDROID_TRUSTED) funkcja AppFunction może być wykrywana i wykonywana przez certyfikowane przez platformę asystenty na urządzeniu, które mają uprawnienia systemowe EXECUTE_APP_FUNCTIONS. Jeśli jednak tworzysz funkcje przeznaczone wyłącznie dla wewnętrznego agenta aplikacji (np. sprawdzanie koszyków użytkowników, uzyskiwanie dostępu do lokalnych tokenów lub wykonywanie działań w aplikacji), możesz ograniczyć dostęp wyłącznie do własnej aplikacji, używając stałej ACCESS_LEVEL_SELF:
import androidx.appfunctions.AppFunctionAccessLevel
import androidx.appfunctions.AppFunctionDeclaration
import androidx.appfunctions.AppFunctionService
import androidx.appfunctions.AppFunctionServiceEntryPoint
import androidx.appfunctions.ExperimentalAppFunctionsApi
import androidx.appfunctions.metadata.AppFunctionMetadata
@AppFunctionServiceEntryPoint(
serviceName = "OrderAppFunctionService",
appFunctionXmlFileName = "order_app_functions",
)
abstract class OrderTools : AppFunctionService() {
private val orderRepository = OrderRepository()
/**
* Cancels an order. Restricted exclusively to this app's internal agent.
*/
@OptIn(ExperimentalAppFunctionsApi::class)
@AppFunctionDeclaration(isDescribedByKDoc = true)
@AppFunctionAccessLevel(
level = AppFunctionMetadata.ACCESS_LEVEL_SELF,
isCompatEnforcementEnabled = true,
)
suspend fun cancel_order(orderId: String): CancelOrderResult {
// Only your app's internal agent can invoke this function
val success = orderRepository.cancelOrder(orderId)
return CancelOrderResult(
orderId = orderId,
cancelled = success,
message = if (success) "Cancelled." else "Failed."
)
}
}
Platforma obsługuje 3 poziomy dostępu rozmówcy:
ACCESS_LEVEL_SELF(100): ściśle ograniczone do samej aplikacji hosta. Zewnętrzni asystenci, interfejsy systemowe i inne aplikacje nie mogą wykrywać ani wywoływać funkcji przez IPC. Używaj tego poziomu w przypadku operacji poufnych lub narzędzi, które powinny obsługiwać tylko wewnętrznego agenta aplikacji.ACCESS_LEVEL_SYSTEM(200): ograniczone do uprzywilejowanych wywołujących systemowych z uprawnieniemandroid.Manifest.permission#EXECUTE_APP_FUNCTIONS_SYSTEM(np. systemowy Launcher lub Ostatnie) oraz samej aplikacji hosta.ACCESS_LEVEL_ANDROID_TRUSTED(300, domyślny): otwarty dla certyfikowanych przez platformę agentów na urządzeniu z uprawnieniemEXECUTE_APP_FUNCTIONS, interfejsów systemowych i aplikacji hosta.
4. Rejestrowanie narzędzi w agencie
Funkcje AppFunctions możesz zarejestrować w agencie automatycznie za pomocą metody enableAppFunctions() lub automatycznie za pomocą metody AppFunctionAgentTools.discover():
Automatyczne wykrywanie za pomocą metody enableAppFunctions()
W przypadku prostych aplikacji, w których funkcje AppFunctions są wykonywane automatycznie, gdy model je wywołuje:
import com.google.mlkit.genai.agent.Agent
import com.google.mlkit.genai.agent.AgentRunner
val agent = Agent.builder()
.setName("OrderAssistant")
.setInstructions("Assist users with order tracking.")
.enableAppFunctions(true) // Automatically discovers all AppFunctions in your app package
.setMaxTurns(10)
.build()
val runner = AgentRunner(agent)
Opcjonalnie możesz wywołać metodę addAppFunctionIds("fetch_order_details", "cancel_order"), aby ograniczyć wykrywanie wyłącznie do określonych identyfikatorów AppFunction, zamiast udostępniać wszystkie funkcje w aplikacji.
Automatyczne wykrywanie za pomocą metody AppFunctionAgentTools.discover()
Gdy aplikacja musi sprawdzić wykryte funkcje AppFunctions, przefiltrować je lub udekorować przed zarejestrowaniem ich u agenta:
import com.google.mlkit.genai.agent.Agent
import com.google.mlkit.genai.agent.AgentRunner
import com.google.mlkit.genai.agent.AppFunctionAgentTools
val discoveredTools = AppFunctionAgentTools.discover(
enabledAppFunctionIds = listOf("fetch_order_details", "cancel_order") // Optional ID filter
)
val agent = Agent.builder()
.setName("OrderAssistant")
.setInstructions("Assist users with order tracking.")
.addTools(discoveredTools)
.build()
val runner = AgentRunner(agent)
Wzorzec dekoratora i proces z udziałem człowieka
Operacje wrażliwe, takie jak anulowanie zamówienia, płatności czy usunięcie danych użytkownika, powinny wymagać potwierdzenia przez człowieka. Wszystkie narzędzia dodane do klasy Agent pochodzą z funkcji aplikacji na Androida, a klasa AgentTool udostępnia konstruktor delegujący AgentTool(delegate), dzięki czemu możesz opakować wykryte narzędzia funkcji aplikacji za pomocą wzorca dekoratora:
import com.google.mlkit.genai.agent.AgentTool
class HitlConfirmationTool(
delegate: AgentTool,
private val onConfirm: suspend (toolName: String, args: Map<String, Any>) -> Boolean
) : AgentTool(delegate) {
override suspend fun execute(args: Map<String, Any>): Map<String, Any?> {
val confirmed = onConfirm(name, args)
if (!confirmed) {
return mapOf(
"status" to "CANCELLED_BY_USER",
"message" to "Action was declined by the user."
)
}
return checkNotNull(delegate).execute(args)
}
}
Sprawdzone metody projektowania narzędzi
- Określ zakres przestrzeni działań: zadbaj o to, aby przestrzeń działań agenta (zestaw narzędzi i uprawnień udostępnionych agentowi) była wystarczająco duża, aby mógł on wykonać swoje zadanie, używając metody
addAppFunctionIds()i stałejACCESS_LEVEL_SELF. Zbyt duża przestrzeń działań sprawia, że model na urządzeniu jest bardziej podatny na błędy i zużywa tokeny okna kontekstu. - Opisy powinny być precyzyjne: Gemini Nano decyduje o wywołaniu narzędzia na podstawie opisów narzędzi i parametrów. Jasno określ cel narzędzia i podaj oczekiwane formaty argumentów (np. daty lub identyfikatory).
- Zwracanie uporządkowanych map błędów: zwracaj opisowe mapy błędów, takie jak
mapOf("error" to "Order not found"), gdy wystąpi oczekiwany błąd. Dzięki temu Gemini Nano może w przystępny sposób wyjaśnić użytkownikowi problem. Jeśli narzędzie zgłosi nieoczekiwany wyjątek, środowisko wykonawcze automatycznie go przechwyci i przekaże komunikat o błędzie z powrotem do modelu w celu samokorekty. - Zwracane dane powinny być skoncentrowane: modele na urządzeniu mają mniejsze okna kontekstu niż modele serwerowe. Zwracaj tylko pola istotne dla odpowiedzi na zapytanie użytkownika, zamiast zrzucać całe wiersze bazy danych lub nieprzetworzone odpowiedzi sieciowe.
- Uruchamiaj operacje wejścia/wyjścia w mechanizmie dispatcher
Dispatchers.IO: zawsze upewnij się, że żądania sieciowe i zapytania do bazy danych Room w narzędziach są wykonywane w mechanizmie dispatcherDispatchers.IO, aby zapobiec blokowaniu elementu wywołującego współprogram.
Dalsze kroki
- Więcej informacji o utrzymywaniu kontekstu rozmowy podczas rotacji aplikacji i czyszczeniu sesji znajdziesz w artykule Zarządzanie sesjami i cyklem życia.
- Obsługuj tokeny tekstu strumieniowego i stany wykonywania narzędzi w sekcji Zdarzenia strumieniowe i integracja z interfejsem.