AppFunctions 品質指南

AppFunctions 實際上等同於 Android 上的 MCP 伺服器。您可以透過工具,與 AI 代理和助理共用應用程式的部分邏輯。

代理會根據說明選取可執行要求的 AppFunction。這項選取程序與傳統 MCP 工具探索類似,AI 應用程式會根據 MCP 伺服器和工具說明做出選擇。應用程式會做為 Android MCP 伺服器,每個 AppFunction 則做為其中一項工具。提供下列說明:

  • 應用程式說明 (Android MCP 伺服器)。應用程式說明是代理瞭解應用程式功能的第一個進入點。為協助代理探索應用程式,請先說明主要領域,並簡要列出 AppFunctions 可完成的工作。
    • 在應用程式中繼資料 XML 檔案中,自行撰寫應用程式說明。
    • 使用應用程式說明,描述個別函式說明無法描述的內容,例如呼叫函式的順序。
  • AppFunction 說明 (工具)。每個函式說明都會解釋一個 AppFunction 的行為。函式說明通常會以動詞 (例如「更新」、「建立」或「預訂」) 和資源 (例如「設定」、「頻道」或「會議室」) 為主。

函式說明指南

代理會依據函式說明,判斷哪個函式符合要求,以及如何呼叫該函式。

撰寫簡潔明確的 AppFunction 說明

說明越明確,代理就越能可靠地為要求選擇正確的函式。下表列出函式說明中應做和應避免的事項:

正確做法 錯誤做法
請以清楚、中性的語言,說明函式的用途和執行結果。 請勿使用說明做為提示。「你必須說」和「你必須詢問使用者」等指令會指示代理如何運作,而非說明函式。
在說明開頭提供重要資訊,並保持簡潔。 請勿在說明中加入「這個函式用於」等無意義的內容。冗詞贅字會讓說明變長,但不會更清楚。
準確反映程式碼的作用,包括參數和輸出內容。例如,標示為唯讀的函式不應寫入或刪除資料。 請勿使用誤導性名稱,例如名為 fetchData 的函式會刪除使用者資料。
以 KDoc 格式編寫函式說明,包括參數和傳回類型。請按照 Kotlin 樣式指南命名,並在不瞭解實作方式的情況下,提供可理解的說明。 請勿過度使用英文大寫字母或特殊字元,吸引使用者注意說明中的特定部分,例如 **CRITICAL** 或 VERY IMPORTANT。
為 AppFunction 使用人類可讀的名稱。
清楚直接地說明每個 AppFunction 的功能。 請勿提供涉及其他函式的程序操作說明,例如「先搜尋聯絡人」。代理會根據應用程式說明規劃函式呼叫,因此請在說明中描述順序。
說明代理可從函式預期獲得的結果,例如「如果 X,則 Y」。
假設代理有自己的 AppFunctions 一般使用指示。函式說明會指出函式的作用,而代理的操作說明則會說明如何使用函式。 請勿嘗試在函式說明中覆寫或嵌入代理層級指令。

編寫時考量封裝

每個函式說明都應獨立完整,引導代理只使用該函式。下表列出封裝規範:

正確做法 錯誤做法
編寫具體明確的函式說明,並著重於單一明確用途。例如「建立、尋找及刪除個人工作」。 請勿撰寫過於廣泛的說明,否則代理可能會在不適當的時間或基於錯誤的原因呼叫函式。
請勿建立多用途 AppFunction,或為相同用途公開多個 AppFunction。如果多個函式的意圖相似,代理較可能選錯函式。
請勿參照或嘗試影響自家應用程式或其他應用程式中其他 AppFunction 的行為。這可能會產生非預期的依附元件和副作用。

允許的操作和資料共用

請遵循下列指南,在代理使用應用程式功能時,保護使用者隱私和安全:

正確做法 錯誤做法
只執行安全、非破壞性的工作,也就是使用者通常會在應用程式中執行的工作,且代理程式可根據使用者要求提供明確的參數,例如建立有標題的工作或搜尋記事。
僅分享非高度個人或機密的資訊,或使用者明確同意分享的資料。 請勿授予代理廣泛的使用者資料或系統資源存取權。
設計 AppFunction 時,請勿要求或傳回密碼、財務詳細資料、私密金鑰或其他高度敏感的個人資訊。
執行使用者要求的動作,即使這些動作在稍後或背景執行,例如在使用者選擇的時間傳送訊息。 請勿執行使用者未要求的動作,例如自行啟動或超出要求範圍的工作。
著重於可透過自然語言提升效益的功能,例如透過對話比透過 UI 導覽更容易表達的工作,例如「[AI 代理],可以幫我查看下週日曆上有哪些空檔,然後建議其中一天飛往芝加哥的航班嗎?」
在手機以外的板型規格上,請尊重使用者在共用裝置 (例如電視、Android Auto 和 Android Automotive OS) 上的隱私權。 請勿揭露應用程式 UI 在該板型規格上不支援的資訊,或執行不支援的操作。
清楚且明確地指出函式的用途。 請勿假冒其他工具或服務。函式的名稱和說明不得與其他工具過於相似,以免代理誤選。

參數和傳回值設計

謹慎設計參數和傳回值,可避免語意模糊,並降低無效呼叫的機率。請遵循下列慣例:

正確做法 錯誤做法
只為函式所需的資料定義參數。 請勿要求函式未使用的資訊。這可盡量減少資料曝光,並簡化函式的使用。
使用能反映預期輸入內容的參數名稱,例如 taskId、searchQuery 或 recipientEmail。 請勿使用 full_context 或 conversation_history 等名稱。這類名稱不夠明確,因為代理會使用這些字詞來表示自己的情境和對話記錄。代理可能會誤解這些資料,並將大量不必要的資料傳遞至函式,導致資料竊取。
請使用 AppFunctions 支援的類型,而非自行定義,例如 LocalDate、LocalTime、LocalDateTime 或 Instant (用於日期和時間),以及 Uri (用於連結)。程式庫在每個應用程式中都以相同方式呈現這些類型,因此代理程式會取得一致的格式。如需支援類型的完整清單,請參閱 AppFunctionSerializable。
如果參數對函式的用途不重要,請將其設為選用。這樣一來,即使代理只取得使用者的部分資訊,也能靈活應對。詳情請參閱「選用參數」。
如果 AppFunction 會更新現有資源,請確認每個參數都能區分「設為空值」和「不變更」。如需範例,請參閱「更新現有資源」。
如要讓代理開啟應用程式中的特定畫面,請傳回該畫面的 PendingIntent。代理程式可以啟動 PendingIntent,而不必為應用程式建構意圖。請使用明確意圖和 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(),
    )
}

更新現有資源

以下範例針對代理程式可更新的每個欄位,使用可為空值的 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,但無法識別失敗類型。