アプリに AppFunctions API を追加する

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

バージョンの互換性

この実装では、プロジェクトの compileSdk を API レベル 36 以上に設定する必要があります。

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

依存関係

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

# 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")
}

AppFunctions ロジックを実装する

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

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

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

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

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

省略可: Hilt を使用してカスタム AppFunction ファクトリを提供する

AppFunction 実装クラスのコンストラクタで依存関係が必要な場合(前の例の TaskRepository など)、システムがインスタンス化の方法を認識できるように、カスタム ファクトリを提供する必要があります。これは省略可能な手順であり、関数クラスにコンストラクタ パラメータがある場合にのみ必要です。この例では、依存関係の注入に Hilt を使用して、カスタム AppFunctionFactory を作成し、Application クラス内で構成する方法を示します。

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

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

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

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

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

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

@AppFunction(isEnabled = false, isDescribedByKDoc = true)
suspend fun createTask(...) { ... }

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

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

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

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

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

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

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

AppFunction の統合を確認する

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

adb shell cmd app_function list-app-functions | grep --after-context 10 $myPackageName を使用して、アプリが提供する AppFunction の詳細を確認します。

Gemini in Android Studio または選択した他のエージェントで、次のようなプロンプトを指定します。

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.