アプリに AppFunctions API を追加する

このガイドでは、AppFunctions API を Android アプリに統合し、関数のロジックを実装して、統合が正しく機能することを確認する方法について説明します。

バージョンの互換性

この実装では、プロジェクト compileSdk を API レベル 37 以降に設定する必要があります。プロジェクトで Android Gradle プラグイン 9.1.0 以降を使用している必要があります。

アプリで AppFunctions がサポートされているかどうかを確認する必要はありません。これは AppFunctions Jetpack ライブラリ内で自動的に処理されます。AppFunctionManager は、機能がサポートされている場合はインスタンスを返し、サポートされていない場合は null を返します。

依存関係

必要なライブラリの依存関係をモジュールの build.gradle.kts(または build.gradle)ファイルに追加し、次のように最上位のアプリ モジュールで KSP プラグインを構成します。

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

AppFunctions ロジックを実装する

Android アプリに AppFunction を実装するには、特定の AppFunctions ロジックを実装するクラスを作成します。これには、パラメータとレスポンスのシリアル化可能なデータクラスを作成し、関数メソッド内でコアロジックを提供することが含まれます。

次のコードは、TODO アプリでタスクを作成する実装例を示しています。これには、カスタム パラメータとレスポンス タイプの定義、リポジトリを使用したメイン関数のロジックが含まれます。

@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.
     */
    @AppFunctionDeclaration(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)
}

コードに関する主なポイント

  • デフォルトでは、AppFunction の実装は Android UI スレッドで実行されます。そのため、長時間実行オペレーションでは次のことを行う必要があります。
    • AppFunction を suspend 関数として宣言します。
    • オペレーションがスレッドをブロックする可能性がある場合は、適切なコルーチン ディスパッチャに切り替えます。
  • isDescribedByKDoc が true に設定されている場合、関数の説明またはシリアル化可能な説明は AppFunctionMetadata の一部としてエンコードされ、エージェントがアプリの AppFunction の使用方法を理解するのに役立ちます。バージョン 1.0.0-beta01 以降、isDescribedByKDoc にはデフォルト値がないため、明示的に設定します。

マニフェストで AppFunction サービスを宣言する

KSP で生成されたサービス宣言と app_metadata プロパティを、モジュール マニフェスト(src/main/AndroidManifest.xml など)に登録します。KSP コンパイラは、抽象エントリ ポイント クラスを拡張する具体的なサービスクラス(TaskAppFunctionService)と、assets/ ディレクトリ内の対応する XML スキーマを生成します。

<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" />

省略可: 実行時に AppFunction の可用性を切り替える

AppFunction をゲーティングする場合は、AppFunctionManager API を使用して関数を明示的に有効または無効にします。ゲーティングは、アプリの特定の機能がすべてのユーザーに利用可能ではない場合に役立ちます。AppFunctions を動的に有効または無効にすることで、インテリジェンス システムは、ユーザーがいつでも利用できる機能を正確に把握できます。

特定のアカウント状態を必要とする AppFunction を安全にゲートするには、次の 2 つの手順を行います。

ステップ 1. 関数をデフォルトで無効にする

フィーチャー トグルが検証される前に関数にアクセスできないようにするには、@AppFunctionDeclaration アノテーションの isEnabled パラメータを false に設定します。

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

ステップ 2. 実行時に機能を動的に有効にする

各 @AppFunctionServiceEntryPoint クラスに対して、コンパイラは関数 ID 定数を含む対応するクラスを生成します(Ids 接尾辞を使用)。これらの生成された ID 定数を AppFunctionManager の setAppFunctionEnabled メソッドとともに使用して、実行時に関数の有効状態を変更できます。

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
    }
}

利用可能にする機能の種類に関する考慮事項

セキュリティは常に最優先事項です。AppFunction として利用可能にするアプリの機能を選択する際は、システム エージェントがサーバー上でユーザー クエリを処理して、高度な LLM 機能を活用する可能性があることに留意する必要があります。

優れたユーザー エクスペリエンスを提供し、機密情報の漏洩を防ぐため、次のガイドラインに沿って対応することをおすすめします。

  • 自然言語のメリットを享受できる機能: ユーザーが手動の UI ナビゲーションよりも会話で簡単に表現できるタスクを利用できるようにします。
  • アクセスを制限する: ユーザーの特定のリクエストを処理するために必要なデータとアクションへのアクセス権のみをエージェントに付与する AppFunction を作成します。
  • 機密情報以外: 個人情報や機密情報ではないデータ、またはユーザーがアクションのコンテキストで共有することに明示的に同意したデータのみを共有します。
  • 破壊的な操作に対する明確な確認: 破壊的な操作(データの削除など)を行う関数には細心の注意を払ってください。エージェントが呼び出す場合もありますが、アプリには独自の確認ステップを含め、意図について明確で曖昧さのない言葉を使用する必要があります。また、複数の確認ステップを追加して、ユーザーが何を求められているかを確実に認識できるようにすることも有効です。

AppFunction の統合を検証する

AppFunctions が正しく統合されているかどうかを確認するには、adb shell cmd app_function を使用します。

adb shell cmd app_function list-app-functions --package $myPackageName を使用して、アプリが提供する AppFunctions の詳細を確認します。

明示的な識別子("$enclosingClassName#$methodName")を使用して、コマンドラインから AppFunction を直接実行することもできます。

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\"}}'"

Android MCP の動作を体験し、プロンプトを必要とせずにエンドツーエンドのワークフローを検証するには、デバイスに AppFunctions テスト エージェント Android アプリをインストールして実行します。

Android Studio の Gemini などのチャットベースのアシスタントを使用して統合を検証する場合は、AppFunctions 開発スキルを使用するか、次のようなプロンプトを指定します。

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.
Use the AppFunction descriptions to choose functions and parameters.

下位の API バージョンから移行する

バージョン 1.0.0-alpha10 では、AppFunctions にコンパイル時の @AppFunctionServiceEntryPoint アーキテクチャが導入されました。これにより、ライブラリの依存関係が統合され、以前の構成プロバイダ(AppFunctionConfiguration.Provider)が置き換えられました。バージョン 1.0.0-alpha12 では、以前のプロバイダが削除されました。バージョン 1.0.0-alpha13 では、@AppFunction アノテーションの名前が @AppFunctionDeclaration に変更されました。バージョン 1.0.0-beta01 では、アプリのメタデータ XML ファイルの appfn:description 属性の名前が appfn:instructions に変更されました。

アプリで以前のバージョンの AppFunctions(1.0.0-alpha09 や 1.0.0-alpha10 など)を使用している場合は、Gemini in Android Studio などの AI IDE で AppFunctions エージェント スキルを使用して移行を自動化できます。このスキルには、ビルド依存関係の統合、必要な @AppFunctionServiceEntryPoint サービス ラッパーの作成、コンテキスト パラメータの分離、マニフェスト宣言の更新、1.0.0-beta01 の名前変更の適用をエージェントに指示する専用の移行ルールが含まれています。