Dodawanie interfejsu AppFunctions API do aplikacji

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 w projekcie compileSdk był ustawiony na poziom API 36 lub wyższy.

Twoja aplikacja nie musi sprawdzać, czy AppFunctions są obsługiwane. Jest to automatycznie obsługiwane w bibliotece AppFunctions Jetpack. AppFunctionManager zwraca instancję, jeśli funkcja jest obsługiwana, a jeśli nie, 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:

dependencies {
  implementation("androidx.appfunctions:appfunctions:1.0.0-alpha10")
  // 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-alpha10")
}

Implementowanie logiki AppFunctions

Aby zaimplementować AppFunction w aplikacji na Androida, utwórz klasę, która implementuje konkretną logikę AppFunctions. Obejmuje to utworzenie serializowalnych klas danych dla parametrów i odpowiedzi, a następnie udostępnienie podstawowej logiki w metodzie funkcji.

Poniższy kod przedstawia przykładową implementację tworzenia zadania w aplikacji TODO, w tym definiowanie niestandardowych parametrów i typów odpowiedzi oraz głównej logiki funkcji za pomocą repozytorium.

@RequiresApi(36)
@AndroidEntryPoint
@AppFunctionServiceEntryPoint(
    serviceName = "TaskAppFunctionService",
    appFunctionXmlFileName = "task_app_function_service",
)
abstract class BaseTaskAppFunctionService : AppFunctionService() {
    @Inject internal lateinit var taskRepository: TaskRepository

    /**
     * Creates a task based on [createTaskParams].
     *
     * @param createTaskParams The parameter to describe how to create the task.
     */
    @AppFunction(isDescribedByKDoc = true)
    suspend fun createTask(
        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@withContext 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:
    • zadeklarować AppFunction jako funkcję zawieszenia;
    • przełączyć się na odpowiedni dyspozytor współprogramu, gdy operacja może zablokować wątek.
  • Gdy isDescribedByKDoc jest ustawione na true, opis funkcji lub opis serializowalny jest kodowany jako część AppFunctionMetadata, aby pomóc agentowi zrozumieć, jak używać AppFunction aplikacji.

Deklarowanie usługi AppFunction w pliku manifestu

Zarejestruj deklarację usługi wygenerowaną przez KSP i właściwość app_metadata w pliku manifestu modułu, np. w src/main/AndroidManifest.xml. Kompilator KSP generuje konkretną klasę usługi (TaskAppFunctionService) rozszerzającą abstrakcyjną klasę punktu wejścia oraz odpowiedni schemat XML w katalogu assets/.

<service
    android:name="com.example.snippets.ai.TaskAppFunctionService"
    android:permission="android.permission.BIND_APP_FUNCTION_SERVICE"
    android:exported="true"
    tools:targetApi="36">
    <property
        android:name="android.app.appfunctions.schema"
        android:value="app_functions_schema.xsd" />
    <property
        android:name="android.app.appfunctions.v2"
        android:value="task_app_function_service.xml" />
    <intent-filter>
        <action android:name="android.app.appfunctions.AppFunctionService" />
    </intent-filter>
</service>
<property
    android:name="android.app.appfunctions.app_metadata"
    android:resource="@xml/app_metadata" />

Opcjonalnie: przełączanie dostępności AppFunction w czasie działania

Aby włączyć lub wyłączyć funkcje podczas ograniczania dostępu do AppFunctions, użyj interfejsu AppFunctionManager API. Ograniczanie dostępu może być przydatne, gdy niektóre funkcje aplikacji nie są dostępne dla wszystkich użytkowników. Dzięki dynamicznemu włączaniu i wyłączaniu AppFunctions system AI dokładnie wie, które funkcje są dostępne dla użytkownika w danym momencie.

Aby bezpiecznie ograniczyć dostęp do AppFunctions, które wymagają określonego stanu konta, wykonaj te 2 czynności:

Krok 1. Domyślne wyłączenie funkcji

Aby uniemożliwić dostęp do funkcji przed zweryfikowaniem flagi funkcji, ustaw parametr isEnabled adnotacji @AppFunction na false.

@AppFunction(isEnabled = false, isDescribedByKDoc = true)
suspend fun createTask(
    createTaskParams: CreateTaskParams,
): Task = TODO()

Krok 2. Dynamiczne włączanie funkcji w czasie działania

W przypadku każdej klasy AppFunction 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 zmienić stan włączenia funkcji w czasie działania.

suspend fun onFeatureEnabled(context: Context) {
    try {
        AppFunctionManager.getInstance(context)
            ?.setAppFunctionEnabled(
                BaseTaskAppFunctionServiceIds.CREATE_TASK_ID,
                AppFunctionManager.APP_FUNCTION_STATE_ENABLED,
            )
    } catch (e: Exception) {
        // Handle exception: AppFunctions indexation may not be fully completed
        // upon initial app startup.
    }
}

suspend fun onFeatureDisabled(context: Context) {
    try {
        AppFunctionManager.getInstance(context)
            ?.setAppFunctionEnabled(
                BaseTaskAppFunctionServiceIds.CREATE_TASK_ID,
                AppFunctionManager.APP_FUNCTION_STATE_DISABLED,
            )
    } catch (e: Exception) {
        // Handle exception
    }
}

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ąć ujawniania poufnych informacji, zalecamy przestrzeganie tych wytycznych:

  • Funkcje, które korzystają z języka naturalnego: udostępnij zadania które użytkownikowi łatwiej jest wyrazić w rozmowie niż za pomocą ręcznej nawigacji po interfejsie.
  • Ograniczony dostęp: utwórz AppFunctions, które zapewniają agentowi dostęp tylko do danych i działań wymaganych do realizacji konkretnego żądania użytkownika.
  • Informacje niepoufne: udostępniaj tylko dane, które nie są wysoce osobiste ani poufne, lub dane, na których udostępnianie użytkownik wyraźnie się zgadza w kontekście działania.
  • Niejednoznaczne potwierdzenie każdej destrukcyjnej czynności: zachowaj szczególną ostrożność w przypadku funkcji, które wykonują destrukcyjne działania (np. usuwanie danych). Chociaż agent może je wywoływać, Twoja aplikacja powinna zawierać własny krok potwierdzenia i używać jasnego, jednoznacznego języka dotyczącego intencji. Warto też dodać więcej niż 1 krok potwierdzenia, aby mieć pewność, że użytkownik wie, o co jest proszony.

Sprawdzanie integracji AppFunction

Aby sprawdzić, czy AppFunctions zostały prawidłowo zintegrowane, możesz użyć adb shell cmd app_function.

Aby zobaczyć szczegóły AppFunctions udostępnianych przez aplikację, użyj adb shell cmd app_function list-app-functions | grep --after-context 10 $myPackageName.

Możesz też wykonać AppFunction bezpośrednio z wiersza poleceń, używając jej jawnego identyfikatora ("$enclosingClassName#$methodName"):

adb shell "cmd app_function execute-app-function \
  --package com.example.android.appfunctions \
  --function 'com.example.android.appfunctions.BaseTaskAppFunctionService#createTask' \
  --parameters '{\"createTaskParams\": {\"title\": \"Buy milk\", \"content\": \"From grocery store\"}}'"

Aby przetestować Android MCP w praktyce i sprawdzić kompleksowe przepływy pracy bez konieczności używania żadnych promptów, zainstaluj i uruchom na urządzeniu aplikację na Androida agenta testującego AppFunctions.

Jeśli sprawdzasz integrację za pomocą asystentów opartych na czacie, takich jak Gemini w Android Studio, użyj umiejętności programowania AppFunctions lub podaj prompta, np.:

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.

Migracja z niższych wersji interfejsu API

W wersji 1.0.0-alpha10 AppFunctions wprowadziło architekturę @AppFunctionServiceEntryPoint w czasie kompilacji, która konsoliduje zależności biblioteki i zastępuje starszych dostawców konfiguracji (AppFunctionConfiguration.Provider).

Jeśli Twoja aplikacja korzysta obecnie ze starszej wersji AppFunctions (np. 1.0.0-alpha09), możesz zautomatyzować migrację za pomocą umiejętności agenta AppFunctions w środowisku AI IDE, takim jak Gemini w Android Studio. Umiejętność zawiera specjalne reguły migracji, które pomagają agentowi skonsolidować zależności kompilacji, utworzyć wymagany otokę usługi @AppFunctionServiceEntryPoint, oddzielić parametry kontekstu i zaktualizować deklaracje w pliku manifestu.

Aby rozpocząć automatyczną migrację za pomocą agenta AI, użyj prompta, np.:

Use the AppFunctions migration skill to upgrade my app's AppFunctions implementation from 1.0.0-alpha09 to the 1.0.0-alpha10 @AppFunctionServiceEntryPoint architecture.