AppFunctions הן המקבילה המעשית של שרתי MCP ב-Android. הם מאפשרים לכם לשתף חלקים מהלוגיקה של האפליקציה עם עוזרים דיגיטליים וסוכני AI.
הנציגים משתמשים בתיאורים כדי לבחור את ה-AppFunction שממלאת בקשה. תהליך הבחירה הזה פועל כמו איתור כלים רגיל של MCP, שבו אפליקציות AI משתמשות בתיאורים של שרתי MCP וכלים של MCP כדי להנחות את הבחירות שלהן. האפליקציה פועלת כשרת MCP של Android, וכל AppFunction פועלת כאחד מהכלים שלו. מוסיפים את התיאורים הבאים:
- תיאור האפליקציה (שרת ה-MCP של Android). תיאור האפליקציה הוא נקודת הכניסה הראשונה שדרכה הסוכנים מבינים מה האפליקציה יכולה לעשות. כדי לעזור לסוכנים לגלות את האפליקציה, כדאי להתחיל עם הדומיין הראשי שלה ולפרט בקצרה את המשימות שהיא יכולה לבצע באמצעות AppFunctions.
- לכתוב את תיאור האפליקציה בעצמכם, בקובץ ה-XML של המטא-נתונים של האפליקציה.
- השתמשו בתיאור האפליקציה כדי לתאר את מה שאי אפשר לתאר בתיאורים של פונקציות ספציפיות, כמו הסדר שבו צריך להפעיל את הפונקציות.
- תיאורים של AppFunction (הכלים). כל תיאור פונקציה מסביר את ההתנהגות של AppFunction אחת. תיאורי הפונקציות בדרך כלל מורכבים מפועל, כמו עדכון, יצירה או הזמנה, וממשאב, כמו הגדרות, ערוץ או חדר.
הנחיות לתיאור הפונקציה
הנציגים מסתמכים על תיאורי הפונקציות כדי להחליט איזו פונקציה מתאימה לבקשה ואיך לקרוא לה.
כתיבת תיאורים תמציתיים וחד-משמעיים של AppFunction
ככל שהתיאור פחות מעורפל, כך הסוכן יכול לבחור בצורה מהימנה יותר את הפונקציה המתאימה לבקשה. בטבלה הבאה מפורט מה כדאי לעשות ומה כדאי להימנע ממנו בתיאורי פונקציות:
| מה מומלץ לעשות | מה אסור לעשות |
|---|---|
| תארו את מטרת הפונקציה ואת התוצאה של הפעלתה, בשפה ברורה ונייטרלית. | אל תשתמשו בתיאורים כהנחיות. פקודות כמו "אתה חייב להגיד" ו"אתה חייב לבקש מהמשתמש" מנחות את הנציג איך להתנהג במקום לתאר את הפונקציה. |
| הציגו את המידע החשוב בתחילת התיאור, והקפידו על תיאורים תמציתיים. | אל תרפדו את התיאורים במילים מיותרות, כמו "הפונקציה הזו משמשת כדי". טקסט מיותר מאריך את התיאור בלי להפוך אותו לברור יותר. |
| לשקף במדויק את מה שהקוד עושה, כולל הפרמטרים והפלט שלו. לדוגמה, פונקציה שמתוארת כפונקציה לקריאה בלבד לא אמורה לכתוב או למחוק נתונים. | אל תשתמשו בשמות מטעים, כמו פונקציה בשם fetchData שמוחקת נתוני משתמשים. |
| כותבים תיאורי פונקציות בפורמט KDoc, כולל פרמטרים וסוג ההחזרה. צריך לפעול לפי מדריך הסגנון של Kotlin כדי לתת שמות, וליצור תיאורים שאפשר להבין בלי לדעת איך להטמיע אותם. | אל תשתמשו באותיות רישיות או בתווים מיוחדים באופן מוגזם כדי למשוך תשומת לב לחלקים בתיאור, כמו **CRITICAL** או VERY IMPORTANT. |
| השתמשו בשמות קריאים לאנשים בשביל AppFunctions. | |
| הסבירו בצורה ברורה וישירה מה כל AppFunction עושה. | אל תתנו הוראות פרוצדורליות שכוללות פונקציות אחרות, כמו "קודם צריך לחפש את איש הקשר". הסוכנים משתמשים בתיאור האפליקציה כדי לתכנן קריאות לפונקציות, אז כדאי לתאר את הסדר הזה שם. |
| תארו מה הסוכן יכול לצפות מהפונקציה, באמצעות ניסוח כמו "אם יש לך X, אז Y". | |
| נניח שלסוכנים יש הוראות משלהם לשימוש ב-AppFunctions באופן כללי. תיאור הפונקציה מסביר מה היא עושה, וההוראות של הנציג מסבירות איך להשתמש בה. | אל תנסו לשנות או להטמיע הוראות ברמת הנציג בתיאורי הפונקציות. |
כתיבה תוך התחשבות באנקפסולציה
חשוב שכל תיאור של פונקציה יהיה עצמאי, כדי שהוא ינחה את הנציג להשתמש בפונקציה הזו ולא באחרת. בטבלה הבאה מפורטות הנחיות לאנקפסולציה:
| מה מומלץ לעשות | מה אסור לעשות |
|---|---|
| כתבו תיאורי פונקציות ספציפיים וממוקדים, עם מטרה אחת מוגדרת היטב. לדוגמה, "ליצור, למצוא ולמחוק משימות אישיות". | אל תכתבו תיאור רחב מדי, כי זה עלול לגרום לסוכן להפעיל את הפונקציה בזמנים לא מתאימים או מהסיבות הלא נכונות. |
| אל תיצרו פונקציות AppFunction עם כמה מטרות, ואל תחשפו הרבה פונקציות AppFunction לאותו תרחיש שימוש. יש סיכוי גבוה יותר שהנציגים יבחרו בפונקציה הלא נכונה כשיש כמה פונקציות עם כוונות דומות. | |
| אל תפנו לפונקציות אחרות של האפליקציה או תנסו להשפיע על ההתנהגות שלהן באפליקציה שלכם או באפליקציות אחרות. זה עלול ליצור תלויות לא מכוונות ותופעות לוואי. |
פעולות מותרות ושיתוף נתונים
כדי להגן על הפרטיות והבטיחות של המשתמשים כשהסוכנים משתמשים בפונקציות של האפליקציה, צריך לפעול לפי ההנחיות הבאות:
| מה מומלץ לעשות | מה אסור לעשות |
|---|---|
| לבצע רק משימות בטוחות ולא הרסניות שהמשתמש היה מבצע בדרך כלל באפליקציה, שבהן הסוכן יכול לספק פרמטרים ברורים מהבקשה של המשתמש – לדוגמה, יצירת משימה עם כותרת או חיפוש הערות. | |
| לשתף רק מידע שהוא לא אישי או סודי מאוד, או נתונים שהמשתמש הסכים במפורש לשתף לצורך הפעולה. | אל תתנו לסוכן גישה נרחבת לנתוני משתמשים או למשאבי מערכת. |
| אל תתכננו פונקציות AppFunction שדורשות או מחזירות סיסמאות, פרטים פיננסיים, מפתחות פרטיים או מידע אישי רגיש אחר. | |
| לבצע פעולות שהמשתמש ביקש, גם אם הן פועלות מאוחר יותר או ברקע – לדוגמה, שליחת הודעה בזמן שהמשתמש בחר. | אל תבצעו פעולות שהמשתמש לא ביקש, כמו משימות שמתחילות מעצמן או חורגות מהבקשה. |
| התמקדו בפונקציונליות שמתאימה לשפה טבעית, כמו משימות שקל יותר לבטא בשיחה מאשר בניווט בממשק המשתמש. לדוגמה, "[סוכן AI], תוכל לעזור לי למצוא זמנים פנויים ביומן בשבוע הבא, ואז להציע טיסה לשיקגו באחד מהימים האלה?" | |
| בגורמי צורה שהם לא טלפונים, צריך לכבד את פרטיות המשתמשים במכשירים משותפים, כמו טלוויזיות, Android Auto ו-Android Automotive OS. | אל תחשפו מידע ואל תבצעו פעולות שממשק המשתמש של האפליקציה לא תומך בהן בגורם הצורה הזה. |
| זהו את מטרת הפונקציה בצורה ברורה וייחודית. | אסור להתחזות לכלי או לשירות אחר. השם והתיאור של הפונקציה לא יכולים להיות דומים באופן מבלבל לכלים אחרים, כי זה עלול לגרום לסוכן לבחור את הפונקציה בטעות. |
עיצוב של פרמטרים וערכי החזרה
תכנון קפדני של הפרמטרים וערכי ההחזרה מונע אי-בהירות ומפחית את הסיכוי לקריאות לא חוקיות. חשוב להקפיד על הכללים הבאים:
| מה מומלץ לעשות | מה אסור לעשות |
|---|---|
| הגדירו פרמטרים רק לנתונים שהפונקציה צריכה. | אל תבקשו מידע שהפונקציה לא משתמשת בו. כך מצמצמים את החשיפה של הנתונים ומפשטים את השימוש בפונקציה. |
השתמשו בשמות פרמטרים שמשקפים את הקלט הצפוי, כמו taskId, searchQuery או recipientEmail. |
אל תשתמשו בשמות כמו full_context או conversation_history. שמות כאלה הם לא חד-משמעיים כי הסוכנים משתמשים במונחים האלה בהקשר ובהיסטוריית השיחות שלהם. הנציג עלול לפרש אותם בצורה שגויה ולהעביר כמות גדולה של נתונים מיותרים לפונקציה, מה שיוביל לזליגת נתונים. |
השתמשו בסוגים שנתמכים ב-AppFunctions במקום להגדיר סוגים משלכם – לדוגמה, LocalDate, LocalTime, LocalDateTime או Instant לתאריכים ולשעות, ו-Uri לקישורים. הספרייה מייצגת את הסוגים האלה באותו אופן בכל אפליקציה, כך שהסוכנים מקבלים פורמט עקבי. לרשימה המלאה של הסוגים הנתמכים, ראו AppFunctionSerializable. |
|
| אם פרמטר מסוים לא חיוני למטרת הפונקציה, הגדירו אותו כאופציונלי. כך הסוכן יכול לפעול בגמישות גם כשיש לו רק מידע חלקי מהמשתמש. מידע נוסף זמין בקטע פרמטרים אופציונליים. | |
| אם הפונקציה AppFunction מעדכנת משאבים קיימים, חשוב לוודא שכל פרמטר יכול להבחין בין "מוגדר כ-null" לבין "לא לשנות". לדוגמה, ראו עדכונים למשאבים קיימים. | |
כדי לאפשר לסוכן לפתוח מסך ספציפי באפליקציה, מחזירים
PendingIntent
למסך הזה. הסוכן יכול להפעיל את ה-PendingIntent כמו שהוא, בלי ליצור Intent לאפליקציה. צריך ליצור אותו עם Intent מפורש ועם FLAG_IMMUTABLE כדי שהסוכן לא יוכל לשנות את המקום שאליו הוא מוביל. |
פרמטרים אופציונליים
רק סוגים שניתנים לאיפוס, פרימיטיביים, רשימות ומערכים יכולים להיות אופציונליים. כשהנציג משמיט פרמטר אופציונלי, הספרייה מעבירה את ערך ברירת המחדל של הסוג – null, אפס, false או ריק – במקום ערך ברירת המחדל שאתם מצהירים עליו.
לפתור ערכי ברירת מחדל משמעותיים בגוף הפונקציה, כמו בדוגמה הבאה:
/**
* Creates a note.
*
* @param title The title of the note.
* @param content The body text of the note.
* @param attachments The files to attach to the note.
* @param owner The ID of the user who owns the note.
* @return The created note.
*/
@AppFunctionDeclaration(isDescribedByKDoc = true)
suspend fun createNote(
title: String, // Required
content: String? = null, // Optional
attachments: List<Attachment> = emptyList(), // Optional
owner: String? = null, // Optional
): Note = withContext(Dispatchers.IO) {
noteRepository.createNote(
title = title,
// The library ignores declared default values and passes null for
// omitted nullable parameters, so resolve meaningful defaults here.
content = content ?: "",
attachments = attachments,
owner = owner ?: currentUserId(),
)
}
עדכונים למשאבים קיימים
בדוגמה הבאה נעשה שימוש ב-wrapper מסוג SetField שניתן להגדיר כ-null לכל שדה שהנציג יכול לעדכן. ערך null ב-SetField פירושו "לא לשנות", ו-SetField(null) פירושו "להגדיר כ-null":
/** A new value for a field of an existing item. */
@AppFunctionSerializable(isDescribedByKDoc = true)
data class SetField<T>(
/** The new value of the field. */
val value: T,
)
/**
* Updates an existing note. Omitted fields keep their values.
*
* @param noteId The ID of the note to update.
* @param title The new title of the note.
* @param content The new body text of the note.
* @return The updated note.
*/
@AppFunctionDeclaration(isDescribedByKDoc = true)
suspend fun updateNote(
noteId: String,
title: SetField<String>? = null,
content: SetField<String?>? = null,
): Note = withContext(Dispatchers.IO) {
val note = noteRepository.getNote(noteId)
?: throw AppFunctionElementNotFoundException(
"No note found with ID $noteId"
)
noteRepository.updateNote(
noteId = noteId,
// A null SetField means "don't change", and SetField(null) means
// "set to null", so don't use ?: for nullable fields.
title = title?.value ?: note.title,
content = if (content != null) content.value else note.content,
)
}
טיפול בשגיאות
אם קריאה נכשלת, הנציג מקבל את החריגה שהפונקציה זורקת, כולל הסוג וההודעה שלה. כדי שהנציג יוכל להתאושש מהכשל או לדווח עליו למשתמש, צריך לפעול לפי המוסכמות הבאות:
| מה מומלץ לעשות | מה אסור לעשות |
|---|---|
מפעילים את מחלקת המשנה AppFunctionException שמתאימה לסיבה, עם הודעה שמתארת את הבעיה בשפה פשוטה. לדוגמה, AppFunctionInvalidArgumentException כשארגומנט לא תקין, או AppFunctionElementNotFoundException כשפריט מבוקש לא קיים. הסוג מציין לנציג איזה סוג של כשל אירע, וההודעה מספקת לו את הפרטים הדרושים כדי להתאושש, כמו תיקון ארגומנט או הודעה למשתמש. הדוגמה של updateNote בקטע עדכונים למשאבים קיימים ממחישה את התבנית הזו. |
אל תאפשרו לחריגות אחרות לחמוק מהפונקציה. הנציג מקבל אותן בתור AppFunctionAppUnknownException, שלא מזהה את סוג הכשל. |