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

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

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

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

האפליקציה לא צריכה לבדוק אם יש תמיכה ב-AppFunctions. זה מטופל באופן אוטומטי בספריית Jetpack של AppFunctions. הפונקציה 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. כדי לעשות את זה, צריך ליצור מחלקות נתונים שניתנות להמרה לסדרת נתונים (serializable) בשביל הפרמטרים והתשובות, ואז לספק את הלוגיקה העיקרית בתוך ה-method של הפונקציה.

הקוד הבא מציג דוגמה להטמעה של יצירת משימה באפליקציית 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 כפונקציית השעיה.
    • עוברים למשלח קורוטינה מתאים כשהפעולה עלולה לחסום את השרשור.
  • כשהערך של 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). אתם יכולים להשתמש בקבועי המזהים שנוצרו לצד ה-method‏ 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
    }
}

שיקולים לגבי סוגי הפונקציות שרוצים להוסיף

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

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

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

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

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

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

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

אם אתם מאמתים את השילוב באמצעות עוזרים דיגיטליים מבוססי-צ'אט כמו 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 ארכיטקטורת @AppFunctionServiceEntryPoint בזמן הידור, שמאחדת את יחסי התלות של הספרייה ומחליפה את ספקי ההגדרות הקודמים (AppFunctionConfiguration.Provider).

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