הוספת 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

כדי להטמיע AppFunction באפליקציית Android, יוצרים מחלקה שמטמיעה את הלוגיקה הספציפית של 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 פועלת בשרשור UI של Android. לכן, פעולה ממושכת צריכה לבצע את הפעולות הבאות:
    • מצהירים על AppFunction כפונקציית השהיה.
    • מעבר ל-Coroutine dispatcher מתאים כשהפעולה עלולה לחסום את ה-thread.
  • כשהערך של isDescribedByKDoc מוגדר כ-true, תיאור הפונקציה או התיאור שניתן לסדרת, מקודדים כחלק מ-AppFunctionMetadata כדי לעזור לסוכן להבין איך להשתמש ב-AppFunction של האפליקציה.

אופציונלי: שימוש ב-Hilt כדי לספק מפעל AppFunction בהתאמה אישית

אם מחלקת ההטמעה של AppFunction דורשת תלות ב-constructor שלה (כמו ב-TaskRepository בדוגמה הקודמת), צריך לספק factory מותאם אישית כדי שהמערכת תדע איך ליצור מופע שלה. זהו שלב אופציונלי שנדרש רק אם למחלקת הפונקציה יש פרמטרים של בנאי. בדוגמה הזו אפשר לראות איך ליצור AppFunctionFactory מותאם אישיתAppFunctionFactory ולהגדיר אותו בתוך המחלקה Application באמצעות Hilt להזרקת תלות.

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, צריך להשתמש ב-API‏ AppFunctionManager כדי להפעיל או להשבית פונקציות באופן מפורש. הגבלת גישה יכולה להיות שימושית אם תכונות מסוימות באפליקציה לא זמינות לכל המשתמשים. הפעלת AppFunctions או השבתתן באופן דינמי מאפשרת למערכת החכמה לדעת בדיוק אילו תכונות זמינות למשתמש בכל רגע נתון.

כדי להגביל בבטחה את הגישה ל-AppFunctions שדורשות מצב חשבון ספציפי, צריך לבצע תהליך דו-שלבי:

שלב 1. השבתה של הפונקציה כברירת מחדל

כדי למנוע גישה לפונקציה לפני אימות feature flag, צריך להגדיר את הפרמטר isEnabled של האנוטציה @AppFunction לערך false.

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

שלב 2. הפעלה דינמית של הפונקציה בזמן הריצה

לכל מחלקה AppFunction, הקומפיילר יוצר מחלקה תואמת שמכילה קבועים של מזהי פונקציות (עם הסיומת Ids). אפשר להשתמש בקבועי המזהים שנוצרו לצד השיטה setAppFunctionEnabled מ-AppFunctionManagerCompat כדי לשנות את מצב ההפעלה של פונקציה בזמן ריצה.

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 כדי לראות פרטים על פונקציות האפליקציה שהאפליקציה מספקת.

ב-Gemini ב-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.