دستورالعمل‌های کیفیت AppFunctions

‫AppFunctions معادل کاربردی سرورهای MCP در Android است. این ابزارها به شما امکان می‌دهند بخش‌هایی از منطق برنامه‌تان را با دستیارها و عوامل هوش مصنوعی هم‌رسانی کنید.

عامل‌ها از شرح‌ها برای انتخاب AppFunction که درخواست را برآورده می‌کند استفاده می‌کنند. این فرایند انتخاب مانند کاوش ابزار MCP سنتی عمل می‌کند، که در آن برنامه‌های هوش مصنوعی از سرور MCP و شرح ابزار برای هدایت انتخاب‌های خود استفاده می‌کنند. برنامه شما به‌عنوان سرور MCP Android عمل می‌کند و هر AppFunction به‌عنوان یکی از ابزارهای آن عمل می‌کند. شرح‌های زیر را ارائه کنید:

  • شرح برنامه (سرور Android MCP). شرح برنامه شما اولین نقطه ورود برای عامل‌ها است تا متوجه شوند برنامه شما چه کاری می‌تواند انجام دهد. برای کمک به عامل‌ها در کشف برنامه شما، با دامنه اصلی آن شروع کنید و به‌طور خلاصه تکالیفی را که AppFunctions شما اجازه می‌دهد انجام دهد فهرست کنید.
    • شرح برنامه را خودتان در فایل XML فراداده برنامه‌تان بنویسید.
    • از شرح برنامه برای توصیف آنچه شرح‌های تابع فردی نمی‌توانند توصیف کنند استفاده کنید، مثل ترتیب فراخوانی توابع.
  • شرح AppFunction (ابزارها). هر شرح تابع رفتار یک AppFunction را توضیح می‌دهد. شرح عملکردها معمولاً حول یک فعل، مانند به‌روزرسانی، ایجاد، یا رزرو، و یک منبع، مانند تنظیمات، کانال، یا اتاق، ساختار می‌یابد.

دستورالعمل‌های شرح تابع

عوامل برای تصمیم‌گیری درباره اینکه کدام تابع با درخواست مطابقت دارد و نحوه فراخوانی آن، به شرح تابع تکیه می‌کنند.

شرح‌های AppFunction را مختصر و بدون ابهام بنویسید

هرچه شرح کمتر مبهم باشد، نماینده با اطمینان بیشتری می‌تواند تابع مناسب را برای درخواست انتخاب کند. جدول زیر کارهایی را که باید در شرح تابع انجام دهید و کارهایی را که باید از آن‌ها اجتناب کنید فهرست می‌کند:

انجام دهید نبایدها
هدف تابع و نتیجه اجرای آن را با زبانی واضح و بی‌طرفانه شرح دهید. از شرح‌ها به‌عنوان پیام‌واره استفاده نکنید. فرمان‌هایی مثل «باید بگویید» و «باید از کاربر بخواهید» به‌جای توصیف عملکرد، به نماینده دستور می‌دهند که چگونه رفتار کند.
اطلاعات کلیدی را در ابتدای شرح قرار دهید و شرح را مختصر نگه دارید. شرح‌ها را با کلمات پرکننده، مثل «این تابع برای … استفاده می‌شود» پر نکنید. نوشتار پرکننده باعث طولانی‌تر شدن شرح می‌شود بدون اینکه آن را واضح‌تر کند.
آنچه را کد انجام می‌دهد، ازجمله پارامترها و خروجی آن، به‌طور دقیق منعکس کند. برای مثال، تابعی که به‌عنوان فقط خواندنی توصیف شده است نباید داده‌ها را بنویسد یا حذف کند. از نام‌های گمراه‌کننده استفاده نکنید، مثلاً تابعی به‌نام fetchData که داده‌های کاربر را حذف می‌کند.
توضیحات تابع را در قالب KDoc بنویسید، ازجمله پارامترها و نوع پاسخ. برای نام‌گذاری، از راهنمای سبک Kotlin پیروی کنید و شرح‌ها را به‌گونه‌ای بنویسید که بدون اطلاع از پیاده‌سازی قابل‌درک باشند. از حروف بزرگ یا نویسه‌های خاص بیش‌ازحد برای جلب توجه به بخش‌هایی از شرح استفاده نکنید، مانند **CRITICAL** یا VERY IMPORTANT.
از نام‌های قابل‌خواندن برای انسان برای AppFunctions استفاده کنید.
درباره کاری که هر AppFunction انجام می‌دهد واضح و مستقیم باشید. دستورالعمل‌های رویه‌ای که شامل عملکردهای دیگر می‌شود ارائه نکنید، مثلاً «ابتدا مخاطب را جستجو کنید». عامل‌ها از شرح برنامه شما برای برنامه‌ریزی فراخوانی‌ها در سراسر توابع استفاده می‌کنند، بنابراین آن ترتیب را در آنجا شرح دهید.
آنچه را که نماینده می‌تواند از تابع انتظار داشته باشد با زبانی مانند «اگر X را دارید، پس Y» توصیف کنید.
فرض کنید که عامل‌ها دستورالعمل‌های خاص خود را برای استفاده از AppFunctions به‌طور کلی دارند. شرح تابع شما می‌گوید تابع چه کاری انجام می‌دهد و دستورالعمل‌های نماینده نحوه استفاده از آن را پوشش می‌دهد. سعی نکنید دستورالعمل‌های سطح عامل را در شرح تابع لغو یا جاسازی کنید.

نوشتن با درنظر گرفتن کپسوله‌سازی

شرح هر تابع را مستقل نگه دارید تا نماینده را در استفاده از آن تابع و نه تابع دیگری راهنمایی کند. جدول زیر دستورالعمل‌های کپسوله‌سازی را فهرست می‌کند:

انجام دهید نبایدها
شرح‌های تابع را به‌صورت خاص و متمرکز، با یک هدف واحد و به‌خوبی تعریف‌شده بنویسید. برای مثال، «ایجاد، پیدا کردن، و حذف تکالیف شخصی.» شرحی ننویسید که بیش‌ازحد کلی باشد، زیرا این کار می‌تواند باعث شود عامل تابع شما را در زمان‌های نامناسب یا به‌دلایل اشتباه فراخوانی کند.
‫AppFunction چندمنظوره ایجاد نکنید یا چندین AppFunction را برای یک مورد استفاده یکسان ارائه نکنید. وقتی چند تابع هدف‌های مشابهی داشته باشند، احتمال اینکه عامل‌ها تابع اشتباهی را انتخاب کنند بیشتر است.
به رفتار سایر AppFunction در برنامه خود یا در برنامه‌های دیگر ارجاع ندهید یا سعی نکنید بر آن تأثیر بگذارید. این کار می‌تواند وابستگی‌های ناخواسته و عوارض جانبی ایجاد کند.

کنش‌های مجاز و هم‌رسانی داده

هنگامی که نماینده‌ها از عملکردهای برنامه‌تان استفاده می‌کنند، این دستورالعمل‌ها را برای محافظت از حریم خصوصی و ایمنی کاربر دنبال کنید:

انجام دهید نبایدها
فقط کارهای ایمن و غیرمخرب را که کاربر معمولاً در برنامه شما انجام می‌دهد، انجام دهید، جایی که نماینده می‌تواند پارامترهای واضحی را از درخواست کاربر ارائه دهد—برای مثال، ایجاد یک کار با عنوان یا جستجوی یادداشت‌ها.
فقط اطلاعاتی را هم‌رسانی کنید که بسیار شخصی یا محرمانه نیستند، یا داده‌هایی را هم‌رسانی کنید که کاربر صریحاً با هم‌رسانی آن‌ها برای کنش موافقت کرده است. به عامل دسترسی گسترده به داده‌های کاربر یا منابع سیستم ندهید.
‫AppFunctions را به‌گونه‌ای طراحی نکنید که گذرواژه، جزئیات مالی، کلیدهای خصوصی، یا دیگر اطلاعات شخصی بسیار حساس را درخواست کند یا برگرداند.
انجام کنش‌هایی که کاربر درخواست کرده است، حتی زمانی که بعداً یا در پس‌زمینه اجرا می‌شوند—برای مثال، ارسال پیام در زمانی که کاربر انتخاب کرده است. کنش‌هایی را که کاربر درخواست نکرده است انجام ندهید، مثلاً تکالیفی که خودبه‌خود شروع می‌شوند یا فراتر از درخواست هستند.
روی عملکردهایی تمرکز کنید که از زبان طبیعی بهره می‌برند، مثل تکالیفی که بیان آن‌ها در مکالمه آسان‌تر از پیمایش واسط کاربر است— برای مثال، «[عامل هوش مصنوعی]، لطفاً کمکم کن چند زمان آزاد در تقویمم برای هفته آینده پیدا کنم، و سپس پروازی به شیکاگو در یکی از آن روزها پیشنهاد بده؟»
در ضریب‌های شکل غیراز تلفن، به حریم خصوصی کاربر در دستگاه‌های مشترک، مثل تلویزیون، Android Auto، و سیستم عامل Android Automotive احترام بگذارید. اطلاعاتی را که واسط کاربر برنامه‌تان در آن ضریب شکل پشتیبانی نمی‌کند فاش نکنید یا کنش‌هایی را که پشتیبانی نمی‌کند انجام ندهید.
هدف تابع را به‌طور واضح و منحصربه‌فرد مشخص کنید. وانمود نکنید که ابزار یا سرویس دیگری هستید. نام و شرح تابع شما نباید به‌طور گمراه‌کننده‌ای شبیه به ابزارهای دیگر باشد، زیرا ممکن است باعث شود نماینده تابع شما را به‌اشتباه انتخاب کند.

طراحی پارامتر و مقدار برگشتی

طراحی دقیق پارامتر و مقدار برگشتی از ابهام جلوگیری می‌کند و احتمال فراخوانی‌های نامعتبر را کاهش می‌دهد. این قراردادها را دنبال کنید:

انجام دهید نبایدها
پارامترها را فقط برای داده‌هایی که تابع شما نیاز دارد تعریف کنید. اطلاعاتی را که تابع از آن استفاده نمی‌کند درخواست نکنید. این کار نمایش داده‌ها را به‌حداقل می‌رساند و استفاده از تابع را ساده می‌کند.
از نام‌های پارامتری استفاده کنید که ورودی موردانتظار را منعکس می‌کنند، مانند taskId، searchQuery، یا recipientEmail. از نام‌هایی مثل full_context یا conversation_history استفاده نکنید. این نام‌ها مبهم هستند زیرا عامل‌ها از این اصطلاحات برای بافت و سابقه مکالمه خودشان استفاده می‌کنند. ممکن است عاملی آن‌ها را اشتباه تفسیر کند و مقدار زیادی داده غیرضروری را به تابع شما منتقل کند، که منجر به استخراج داده‌ها می‌شود.
به‌جای تعریف کردن انواع خودتان، از انواعی که AppFunctions پشتیبانی می‌کند استفاده کنید—برای مثال، LocalDate،‏ LocalTime، LocalDateTime، یا Instant برای تاریخ و زمان، و Uri برای پیوندها. کتابخانه این نوع‌ها را در همه برنامه‌ها به یک شکل نشان می‌دهد، بنابراین عامل‌ها قالب ثابتی دریافت می‌کنند. برای مشاهده فهرست کامل انواع پشتیبانی‌شده، به AppFunctionSerializable مراجعه کنید.
اگر پارامتری برای هدف تابع ضروری نیست، آن را اختیاری کنید. این کار به نماینده انعطاف‌پذیری می‌دهد وقتی که فقط اطلاعات جزئی از کاربر دارد. برای اطلاعات بیشتر، پارامترهای اختیاری را ببینید.
اگر AppFunction شما منابع موجود را به‌روز می‌کند، مطمئن شوید که هر پارامتر بتواند بین «تنظیم روی null» و «تغییر نده» تمایز قائل شود. برای مثال، به‌روزرسانی منابع موجود را ببینید.
برای اینکه به نماینده اجازه دهید صفحه خاصی را در برنامه‌تان باز کند، PendingIntent را برای آن صفحه برگردانید. نماینده می‌تواند PendingIntent را همان‌طور که هست، بدون ساختن قصد برای برنامه‌تان، راه‌اندازی کند. آن را با قصد صریح و FLAG_IMMUTABLE ایجاد کنید تا نماینده نتواند مقصد آن را تغییر دهد.

پارامترهای اختیاری

فقط انواع nullable،‏ primitive،‏ list، و array می‌توانند اختیاری باشند. وقتی نماینده پارامتر اختیاری را حذف می‌کند، کتابخانه مقدار پیش‌فرض نوع را ارسال می‌کند—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(),
    )
}

به‌روزرسانی منابع موجود

مثال زیر از یک پوشاننده SetField با قابلیت تهی شدن برای هر فیلدی که نماینده می‌تواند به‌روز کند استفاده می‌کند. SetField تهی یعنی «تغییر ندهید»، و SetField(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 دریافت می‌کند، که نوع خرابی را مشخص نمی‌کند.