הוספת AppFunctions API לאפליקציה

במדריך הזה מוסבר איך לשלב את AppFunctions API באפליקציית Android, להטמיע את הלוגיקה של פונקציה ולוודא שהשילוב פועל בצורה תקינה.

תאימות גרסאות

כדי להטמיע את התכונה הזו, צריך להגדיר את הפרויקט compileSdk לרמת API‏ 36 ומעלה.

אין צורך לאמת באפליקציה אם יש תמיכה ב-AppFunctions. הטיפול בזה מתבצע אוטומטית בספריית AppFunctions Jetpack. הפונקציה AppFunctionManager מחזירה מופע אם התכונה נתמכת, ומחזירה ערך null אם לא.

פניות קשורות

מוסיפים את יחסי התלות הנדרשים של הספרייה לקובץ build.gradle.kts (או build.gradle) של המודול, ומגדירים את הפלאגין KSP במודול האפליקציה ברמה העליונה כמו שמוצג:

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

הטמעה של לוגיקה של AppFunctions

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

הצהרה על שירות AppFunction במניפסט

רושמים את הצהרת השירות שנוצרה על ידי KSP ואת המאפיין app_metadata במניפסט של המודול, למשל ב-src/main/AndroidManifest.xml. הקומפיילר של KSP יוצר את מחלקת השירות הקונקרטית (TaskAppFunctionService) שמרחיבה את מחלקת נקודת הכניסה המופשטת, יחד עם סכמת ה-XML התואמת בספרייה assets/.

<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 בזמן ריצה

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

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

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

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

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

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

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

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

שיקולים לגבי סוגי הפונקציות שרוצים להפוך לזמינות

האבטחה תמיד נמצאת בראש סדר העדיפויות. כשבוחרים אילו יכולות של האפליקציה להפוך לפונקציות אפליקציה, חשוב לזכור שאגנטים של המערכת עשויים לעבד שאילתות של משתמשים בשרת כדי להשתמש ביכולות מתקדמות של מודלים גדולים של שפה (LLM).

כדי לספק חוויית משתמש מעולה וגם למנוע חשיפה של מידע רגיש, מומלץ לפעול לפי ההנחיות הבאות:

  • פונקציונליות שמתבססת על שפה טבעית: מאפשרת למשתמשים לבצע משימות בקלות רבה יותר באמצעות שיחה, במקום באמצעות ניווט ידני בממשק המשתמש.
  • גישה מצומצמת: יוצרים AppFunctions שנותנות לסוכן גישה רק לנתונים ולפעולות שנדרשים כדי למלא את הבקשה הספציפית של המשתמש.
  • מידע לא רגיש: אפשר לשתף רק נתונים שלא נחשבים אישיים או סודיים, או נתונים שהמשתמש הסכים לשתף באופן מפורש בהקשר של הפעולה.
  • אישור חד-משמעי לכל פעולה הרסנית: צריך לנקוט משנה זהירות כשמשתמשים בפונקציות שמבצעות פעולות הרסניות (כמו מחיקת נתונים). יכול להיות שהסוכן יפעיל אותן, אבל האפליקציה צריכה לכלול שלב אישור משלה ולהשתמש בשפה ברורה וחד-משמעית לגבי הכוונות. כדאי גם להוסיף יותר משלב אישור אחד כדי לוודא שהמשתמש מבין מה מבקשים ממנו לעשות.

אימות השילוב של AppFunction

כדי לוודא ששילבתם את AppFunctions בצורה נכונה, אתם יכולים להשתמש ב-adb shell cmd app_function.

אפשר להשתמש ב-adb shell cmd app_function list-app-functions | grep --after-context 10 $myPackageName כדי לראות פרטים על פונקציות האפליקציה שהאפליקציה מספקת.

אפשר גם להריץ AppFunction ישירות משורת הפקודה באמצעות המזהה המפורש שלה ("$enclosingClassName#$methodName"):

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 בפעולה ולאמת תהליכי עבודה מקצה לקצה בלי להצטרך להנחיות, צריך להתקין ולהפעיל את אפליקציית Android‏ AppFunctions testing agent במכשיר.

אם אתם מאמתים את השילוב באמצעות עוזרים מבוססי-צ'אט כמו Gemini ב-Android Studio, אתם יכולים להשתמש במיומנות הפיתוח 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.
Rely on the AppFunction description as instructions.

מעבר מגרסאות API נמוכות יותר

בגרסה 1.0.0-alpha10, AppFunctions הציגה ארכיטקטורה של זמן קומפילציה (compile-time)@AppFunctionServiceEntryPoint שמאחדת תלות בספריות ומחליפה ספקי הגדרות מדור קודם (AppFunctionConfiguration.Provider).

אם האפליקציה שלכם משתמשת כרגע בגרסה מוקדמת יותר של AppFunctions (כמו 1.0.0-alpha09), אתם יכולים להשתמש בסוכן AppFunctions ב-IDE עם AI כמו Gemini ב-Android Studio כדי להפוך את ההעברה לאוטומטית. המיומנות כוללת כללי מיגרציה ייעודיים שמנחים את הסוכן לאחד את יחסי התלות של ה-build, ליצור את @AppFunctionServiceEntryPoint ה-wrapper של השירות הנדרש, להפריד בין פרמטרים של הקשר ולעדכן את הצהרות המניפסט.

כדי להתחיל העברה אוטומטית באמצעות סוכן ה-AI, משתמשים בהנחיה כמו זו שבהמשך:

Use the AppFunctions migration skill to upgrade my app's AppFunctions implementation from 1.0.0-alpha09 to the 1.0.0-alpha10 @AppFunctionServiceEntryPoint architecture.