向应用添加 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 界面线程中运行。 因此,长时间运行的操作应执行以下操作:
    • 将 AppFunction 声明为挂起函数。
    • 当操作可能会阻塞线程时,切换到合适的协程调度器。
  • isDescribedByKDoc 设置为 true 时,函数说明或可序列化说明会编码为 AppFunctionMetadata 的一部分,以帮助智能体了解如何使用应用的 AppFunction。

可选:使用 Hilt 提供自定义 AppFunction 工厂

如果您的 AppFunction 实现类的构造函数需要依赖项(如上一个示例中的 TaskRepository),则需要提供自定义工厂,以便系统知道如何实例化它。这是一个可选步骤,仅当您的函数类具有构造函数参数时才需要执行此步骤。此示例展示了如何创建自定义 AppFunctionFactory 并使用 Hilt 在 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 可用性

在控制 AppFunctions 时,使用 AppFunctionManager API 显式启用或停用函数。当应用的某些功能并非面向所有用户提供时,控制功能会非常有用。通过动态启用或停用 AppFunctions,智能系统可以准确了解用户在任何给定时间可以使用哪些功能。

如需安全地控制需要特定账号状态的 AppFunctions,请按以下两步流程操作:

第 1 步:将函数设置为默认停用

为防止在验证功能标志之前访问该函数,请将 @AppFunction 注解的 isEnabled 参数设置为 false

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

第 2 步:在运行时动态启用函数

对于每个 AppFunction 类,编译器都会生成一个包含函数 ID 常量(使用 Ids 后缀)的相应类。您可以将这些生成的 ID 常量与 AppFunctionManagerCompat 中的 setAppFunctionEnabled 方法搭配使用,以在运行时更改函数的启用状态。

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

考虑要提供的功能类型

安全始终是重中之重。在选择要作为 AppFunctions 提供的应用功能时,请务必记住,系统智能体可能会在服务器上处理用户查询,以利用高级 LLM 功能。

为提供出色的用户体验,同时避免泄露敏感信息,我们建议您遵循以下准则:

  • 受益于自然语言的功能:提供用户在对话中比通过手动界面导航更容易表达的任务。
  • 缩小访问权限:创建 AppFunctions,仅向智能体授予完成用户特定请求所需的 数据和操作的访问权限。
  • 非敏感信息:仅共享并非高度个人 或机密的数据,或者用户明确同意在 操作上下文中共享的数据。
  • 针对任何破坏性操作的明确确认:对于执行破坏性操作(如删除 数据)的函数,请格外 谨慎。虽然智能体可能会调用这些函数,但您的应用应包含自己的确认步骤,并使用清晰明确的语言说明意图。 添加多个确认步骤也有助于真正确保用户了解系统要求他们执行的操作。

验证 AppFunction 集成

如需验证是否已正确集成 AppFunctions,您可以使用 adb shell cmd app_function

使用 adb shell cmd app_function list-app-functions | grep --after-context 10 $myPackageName 查看应用提供的 AppFunctions 的详细信息。

在 Android Studio 中的 Gemini 或您选择的其他智能体中,提供如下提示。

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.