앱에 AppFunctions API 추가

이 가이드에서는 Android 앱에 AppFunctions API를 통합하고, 함수의 로직을 구현하고, 통합이 올바르게 작동하는지 확인하는 방법을 설명합니다.

버전 호환성

이 구현에서는 프로젝트 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 로직을 구현하는 클래스를 만듭니다. 여기에는 매개변수와 응답을 위한 직렬화 가능한 데이터 클래스를 만든 다음 함수 메서드 내에서 핵심 로직을 제공하는 작업이 포함됩니다.

다음 코드는 저장소를 사용하여 맞춤 매개변수와 응답 유형, 기본 함수 로직을 정의하는 등 할 일 앱에서 작업을 만드는 구현 예를 보여줍니다.

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을 정지 함수로 선언합니다.
    • 작업이 스레드를 차단할 수 있는 경우 적절한 코루틴 디스패처로 전환합니다.
  • isDescribedByKDoctrue로 설정되면 함수 설명이나 직렬화 가능한 설명이 AppFunctionMetadata의 일부로 인코딩되어 에이전트가 앱의 AppFunction을 사용하는 방법을 이해하도록 지원합니다.

선택사항: Hilt를 사용하여 맞춤 AppFunction 팩토리 제공

AppFunction 구현 클래스의 생성자에 종속 항목이 필요한 경우 (이전 예의 TaskRepository와 같이) 시스템이 인스턴스화하는 방법을 알 수 있도록 맞춤 팩토리를 제공해야 합니다. 이 단계는 선택사항이며 함수 클래스에 생성자 매개변수가 있는 경우에만 필요합니다. 이 예에서는 종속 항목 삽입을 위해 Hilt를 사용하여 Application 클래스 내에서 맞춤 AppFunctionFactory를 만들고 구성하는 방법을 보여줍니다.

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 상수를 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를 사용하여 앱에서 제공하는 AppFunctions의 세부정보를 확인합니다.

Android 스튜디오의 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.