Z tego przewodnika dowiesz się, jak zintegrować interfejs AppFunctions API z aplikacją na Androida, zaimplementować logikę funkcji i sprawdzić, czy integracja działa prawidłowo.
Zgodność wersji
Ta implementacja wymaga, aby projekt compileSdk był ustawiony na poziom API 36 lub wyższy.
Aplikacja nie musi sprawdzać, czy funkcje aplikacji są obsługiwane. Jest to automatycznie obsługiwane w bibliotece AppFunctions Jetpack.
Funkcja AppFunctionManager zwraca instancję, jeśli funkcja jest obsługiwana, a w przeciwnym razie zwraca wartość null.
Zależności
Dodaj wymagane zależności biblioteki do pliku build.gradle.kts (lub build.gradle) modułu i skonfiguruj wtyczkę KSP w module aplikacji najwyższego poziomu, jak pokazano poniżej:
# Add this to your app module at the top level. For multi module applications,
# you only need to specify this once.
ksp {
arg("appfunctions:aggregateAppFunctions", "true")
}
dependencies {
implementation("androidx.appfunctions:appfunctions:1.0.0-alpha09")
implementation("androidx.appfunctions:appfunctions-service:1.0.0-alpha09")
// If this project uses any Kotlin source, use Kotlin Symbol Processing (KSP)
// See Add the KSP plugin to your project
ksp("androidx.appfunctions:appfunctions-compiler:1.0.0-alpha09")
}
Implementowanie logiki AppFunctions
Aby wdrożyć AppFunction w aplikacji na Androida, utwórz klasę, która implementuje konkretną logikę AppFunctions. Obejmuje to tworzenie klas danych, które można serializować, na potrzeby parametrów i odpowiedzi, a następnie udostępnianie podstawowej logiki w metodzie funkcji.
Poniższy kod przedstawia przykładową implementację tworzenia zadania w aplikacji TODO, w tym definiowanie parametrów niestandardowych i typów odpowiedzi oraz logiki funkcji głównej za pomocą repozytorium.
package com.example.android.appfunctions
import androidx.appfunctions.AppFunctionSerializable
import androidx.appfunctions.AppFunctionContext
import androidx.appfunctions.AppFunctionElementNotFoundException
import androidx.appfunctions.AppFunctionInvalidArgumentException
import androidx.appfunctions.service.AppFunction
import javax.inject.Inject
...
// Developers can provide additional parameters in the constructor if needed.
// This requires a custom factory setup in the next step.
class TaskFunctions @Inject constructor(
private val taskRepository: TaskRepository
) {
/** The parameter to create the task. */
@AppFunctionSerializable(isDescribedByKDoc = true)
data class CreateTaskParams(
/** The title of the task. */
val title: String,
/** The content of the task. */
val content: String
)
/** The user-created task. */
@AppFunctionSerializable(isDescribedByKDoc = true)
data class Task(
/** The ID of the task. */
val id: String,
/** The title of the task. */
val title: String,
/** The content of the task. */
val content: String
)
/**
* Creates a task based on [createTaskParams].
*
* @param createTaskParams The parameter to describe how to create the task.
*/
@AppFunction(isDescribedByKDoc = true)
suspend fun createTask(
appFunctionContext: AppFunctionContext,
createTaskParams: CreateTaskParams,
): Task = withContext(Dispatchers.IO) {
// Developers can use predefined exceptions to let the agent know
// why it failed.
if (createTaskParams.title == null && createTaskParams.content == null) {
throw AppFunctionInvalidArgumentException("Title or content should be non-null")
}
val id = taskRepository.createTask(
createTaskParams.title,
createTaskParams.content)
return taskRepository
.getTask(id)
?.toTask()
?: throw AppFunctionElementNotFoundException("Task not found for ID = $id")
}
// Maps internal TaskEntity
private fun TaskEntity.toTask() = Task(id = id, title = title, content = description)
}
Najważniejsze informacje o kodzie
- Domyślnie implementacja AppFunction działa w wątku UI Androida.
Dlatego długotrwała operacja powinna wykonać te czynności:
- Zadeklaruj AppFunction jako funkcję zawieszającą.
- Przełącz się na odpowiedni mechanizm dispatcher korutyn, gdy operacja może zablokować wątek.
- Gdy parametr
isDescribedByKDocma wartośćtrue, opis funkcji lub opis serializowalny jest kodowany jako część parametruAppFunctionMetadata, aby pomóc agentowi zrozumieć, jak używać funkcji aplikacji.
Opcjonalnie: użyj Hilta, aby udostępnić niestandardową fabrykę AppFunction
Jeśli klasa implementacji AppFunction wymaga zależności w konstruktorze (tak jak w przypadku klasy TaskRepository w poprzednim przykładzie), musisz podać niestandardową fabrykę, aby system wiedział, jak ją utworzyć. Jest to krok opcjonalny, który jest konieczny tylko wtedy, gdy klasa funkcji ma parametry konstruktora. W tym przykładzie pokazujemy, jak utworzyć niestandardowy element AppFunctionFactory i skonfigurować go w klasie Application za pomocą Hilt do wstrzykiwania zależności.
import android.app.Application
import androidx.appfunctions.service.AppFunctionConfiguration
import com.example.android.appfunctions.TaskFunctions
import dagger.hilt.android.HiltAndroidApp
import javax.inject.Inject
@HiltAndroidApp
class TodoApplication : Application(), AppFunctionConfiguration.Provider {
@Inject lateinit var taskFunctions: TaskFunctions
override fun onCreate() {
super.onCreate()
}
// This shows how AppFunctions works with Hilt.
override val appFunctionConfiguration: AppFunctionConfiguration
get() =
AppFunctionConfiguration.Builder()
.addEnclosingClassFactory(TaskFunctions::class.java) { taskFunctions }
.build()
}
Opcjonalnie: przełączanie dostępności funkcji aplikacji w czasie działania
Aby ograniczyć dostęp do funkcji aplikacji, użyj interfejsu AppFunctionManager API, aby jawnie włączać lub wyłączać funkcje. Ograniczanie dostępu może być przydatne, gdy niektóre funkcje aplikacji są niedostępne dla wszystkich użytkowników. Dzięki dynamicznemu włączaniu i wyłączaniu funkcji aplikacji system AI dokładnie wie, które funkcje są dostępne dla użytkownika w danym momencie.
Aby bezpiecznie ograniczyć dostęp do funkcji aplikacji, które wymagają określonego stanu konta, wykonaj te 2 czynności:
Krok 1. Domyślnie wyłącz funkcję
Aby uniemożliwić dostęp do funkcji przed zweryfikowaniem flagi funkcji, ustaw parametr isEnabled w adnotacji @AppFunction na wartość false.
@AppFunction(isEnabled = false, isDescribedByKDoc = true)
suspend fun createTask(...) { ... }
Krok 2. Dynamiczne włączanie funkcji w czasie działania
Dla każdej AppFunction klasy kompilator generuje odpowiednią klasę zawierającą stałe identyfikatory funkcji (z sufiksem Ids). Możesz używać tych wygenerowanych stałych identyfikatorów wraz z metodą setAppFunctionEnabled z AppFunctionManagerCompat, aby zmieniać stan włączenia funkcji w czasie działania programu.
import androidx.appfunctions.AppFunctionManager
// Assuming there is a hook API to observe user state or feature flags
suspend fun onFeatureEnabled() {
try {
AppFunctionManager.getInstance(context)
.setAppFunctionEnabled(
// Function ID is generated for developer to get
TaskFunctionsIds.CREATE_TASK_ID,
AppFunctionManagerCompat.APP_FUNCTION_STATE_ENABLED,
)
} catch (e: Exception) {
// Handle exception: AppFunctions indexation may not be fully completed
// upon initial app startup.
}
}
suspend fun onFeatureDisabled() {
AppFunctionManagerCompat.getInstance(context)
.setAppFunctionEnabled(
TaskFunctionsIds.CREATE_TASK_ID,
AppFunctionManagerCompat.APP_FUNCTION_STATE_DISABLED,
)
}
Rozważania dotyczące typów funkcji, które mają być dostępne
Bezpieczeństwo jest zawsze najważniejsze. Wybierając funkcje aplikacji, które mają być dostępne jako AppFunctions, pamiętaj, że agenci systemowi mogą przetwarzać zapytania użytkowników na serwerze, aby korzystać z zaawansowanych funkcji LLM.
Aby zapewnić użytkownikom wygodę i uniknąć ujawnienia informacji poufnych, zalecamy przestrzeganie tych wytycznych:
- Funkcje, które korzystają z języka naturalnego: udostępniaj zadania, które użytkownikowi łatwiej jest wyrazić w rozmowie niż za pomocą ręcznej nawigacji po interfejsie.
- Ograniczony dostęp: twórz funkcje aplikacji, które przyznają agentowi dostęp tylko do danych i działań wymaganych do spełnienia konkretnej prośby użytkownika.
- Informacje niewrażliwe: udostępniaj tylko dane, które nie są wysoce osobiste ani poufne, lub dane, na których udostępnianie użytkownik wyraźnie wyraził zgodę w kontekście działania.
- Niejednoznaczne potwierdzenie każdej czynności powodującej utratę danych: zachowaj szczególną ostrożność w przypadku funkcji, które wykonują czynności powodujące utratę danych (np. usuwanie danych). Chociaż agent może je wywoływać, aplikacja powinna zawierać własny krok potwierdzenia i używać jasnego, jednoznacznego języka, aby informować o swoich zamiarach. Warto też dodać więcej niż 1 krok potwierdzenia, aby mieć pewność, że użytkownik wie, o co jest proszony.
Weryfikowanie integracji AppFunction
Aby sprawdzić, czy funkcja AppFunctions została prawidłowo zintegrowana, możesz użyć adb
shell cmd app_function.
Użyj ikony adb shell cmd app_function list-app-functions | grep --after-context 10
$myPackageName, aby wyświetlić szczegóły funkcji aplikacji.
W Gemini w Android Studio lub w innych wybranych przez siebie agentach wpisz prompt, np. taki:
Execute `adb shell cmd app_function` to learn how the tool works, then act as a
chat agent aiming to invoke AppFunctions to fulfil user prompts for this app.
Rely on the AppFunction description as instructions.