教程

Apple AI Agent 如何連接 App、API 和工具?JSON 扮演什麼角色?

系統 Agent 走 App Intents,App 內模型走 Foundation Models 的 Tool。JSON 不是裝飾,而是參數契約、結構化輸出和 HTTP 載荷共用的形狀。

「Apple AI Agent」經常被當成一個產品名。實際落地時,你至少會碰到兩條完全不同的調用鏈:一條是系統級 Apple Intelligence 替用戶選 App、選動作;另一條是你的 App 自己跑 on-device 模型,再由模型決定調用哪些工具。兩條鏈都能碰到 App、API 和工具,但誰擁有會話、誰生成參數、JSON 出現在哪一層,並不一樣。把這三層分清,後面的 Schema、日誌和排障纔有落點。

先分清誰在跑模型

先問一句:這次推理是誰發起的?如果用戶對 Siri 或系統級 Apple Intelligence 說話,模型在系統側(設備上或 Private Cloud Compute),你的 App 只是被路由到的能力提供方。這時你暴露的是 App Intents:類型化的動作、實體和參數。系統 Agent 決定何時調用,你的代碼負責執行。官方入口見App Intents 文檔。

如果用戶已經在你的 App 裏,你用 Foundation Models 起了一個LanguageModelSession,那是另一條鏈:模型由你的進程驅動,工具由你注入。系統不會替你選「打開哪個 App」,因爲會話本來就在 App 內。工具可以查通訊錄、讀日曆、打你自己的網絡 API,再把結果寫回 transcript。框架說明見Foundation Models,工具調用的會話級講解見 WWDC25 的Meet the Foundation Models framework。

第三種情況越來越常見:外部主機(Claude、ChatGPT、自建 Agent)通過 MCP 或普通 HTTPS 調你的服務。模型不在 Apple 棧裏跑,但載荷幾乎一定是 JSON。同一套領域函數可以同時服務 App Intents、Foundation Models Tool 和 HTTP,差別只在適配層。

調用鏈 誰跑模型 App 提供什麼
系統 Agent Apple Intelligence App Intents / App Entity
App 內 Agent 你的 LanguageModelSession Tool 協議與 @Generable 參數
外部 Agent 第三方主機 JSON-RPC 或 REST JSON

不要把三條鏈畫成一張「萬能 Agent 圖」。排障時先定位是哪一條:系統路由失敗,查 Intent 聲明和參數類型;App 內工具亂調,查 name、description 和 Arguments 的可生成形狀;外部 API 對不上,查 HTTP JSON 與 Schema。混在一起只會覺得「模型胡來」。

App Intents:系統 Agent 如何連上你的 App

對系統 Agent 來說,App 不是「打開一下看看」,而是一份可發現的能力目錄。你用 AppIntent 聲明動作名稱、自然語言描述、參數類型和結果。Spotlight、快捷指令、Siri 和 Apple Intelligence 共用這套目錄。用戶說「把這張發票標成已付」,系統要能把話映射到你的MarkInvoicePaid,而不是讓模型在界面上亂點。

參數纔是連接點。系統側會把用戶話語收成類型化參數:枚舉、日期、AppEntity 引用。你在 Swift 裏看到的是結構體,不是一段散文。跨進程或跨框架時,這套結構仍然要有一份可序列化的形狀——調試、日誌、服務端回放,最後都會落到 JSON 或等價的屬性列表。把 Intent 參數設計成「能寫成 JSON 對象」的字段集,後面接 API 會省很多適配。

常見取捨:Intent 要不要直接打網絡?短動作可以在 perform 裏完成。一旦涉及鑑權、分頁、冪等,perform 裏只做「校驗參數 + 調用你的領域服務」,領域服務再發 JSON 請求。系統 Agent 不需要知道你的 REST 路徑,它只需要 Intent 成功或失敗的語義。你需要知道路徑,因爲賬單、審計和重試都在 API 這一層。

Foundation Models:App 內模型如何調用工具

App 內 Agent 的連接方式更像「給模型一份函數說明書」。你實現Tool:給name、description,再給帶@Generable的Arguments。框架把這些寫進 prompt,模型決定何時調用;調用時先生成參數,框架再執行call(arguments:),把返回值(通常是 String 或可生成類型)塞回 transcript。模型據此寫最終答覆。這不是讓模型自己拼 URL,而是讓它在你允許的工具集合裏選。

參數生成走的是結構化輸出,不是「請用 JSON 回答」。@Generable與動態 Schema 把字段、枚舉、嵌套對象釘在解碼階段。你在 Swift 裏拿到的是類型實例;若要落盤、打日誌、轉給 URLSession,再編碼成 JSON。官方把這類能力概括爲 language understanding、structured output 和 tool calling,見 Foundation Models 概覽。

struct FindOrders: Tool {
  let name = "findOrders"
  let description = "Find recent orders by customer email and status."

  @Generable
  struct Arguments {
    var email: String
    var status: String
    var limit: Int
  }

  func call(arguments: Arguments) async throws -> String {
    // Call your domain API, then return a compact summary.
    return "3 orders, latest is paid"
  }
}

上面這段裏,模型不會「隨便發明」字段名。它必須填email、status、limit。description 決定會不會被選中:寫得太寬,模型遇事都調;寫得太窄,該調時不調。工具輸出也要剋制:把整份訂單 JSON 倒進 transcript 會迅速喫掉上下文窗口。更好的做法是返回摘要,需要細節時再調第二個工具拿單號。

工具可以鏈式調用。模型用第一個工具的輸出當作第二個工具的輸入,框架按順序執行。你要在領域層保證冪等:同一orderId標兩次已付,不能扣兩次款。Agent 循環看不見你的數據庫約束,JSON 參數對了不等於業務對了。

JSON 在其中扮演什麼角色

Swift 類型是編譯期契約,JSON 是運行期契約。Agent 一旦跨出當前進程——打後端、寫文件、給另一個模型看、給測試夾具回放——形狀就要變成語言無關的文本。JSON 在這條鏈上至少扮演三層角色,混用其中兩層就會出現「能 parse 但字段全錯」。

1. 工具參數的交換格式

Foundation Models 在內部用 GeneratedContent 表示結構化值。你調試時,最有用的視圖往往是「這份 Arguments 若編碼成 JSON 長什麼樣」。字段名、可選性、數組還是對象,都必須穩定。把參數日誌打成 JSON,才能和線上 API 訪問日誌對齊:同一customerId到底有沒有傳到服務端。

{
  "email": "ada@example.com",
  "status": "paid",
  "limit": 5
}

2. 結構化輸出的 Schema

不想走工具、只想讓模型吐一份發票摘要時,仍然需要 Schema:哪些鍵必填、枚舉有哪些值、金額是 number 還是 string。Apple 側用@Generable;雲端模型常用 JSON Schema。兩套聲明應對同一領域對象時,應用同一份字段表,避免 App 內叫totalCents、API 叫amount。寫 Schema 的共用規則可對照Understanding JSON Schema。

3. App 到 API 的載荷

工具的call裏一旦訪問網絡,JSON 就是 HTTP 的正文。Agent 並不替代 API 設計:鑑權頭、冪等鍵、錯誤對象仍然要你定義。模型只負責填「業務參數」;傳輸層、分頁、限流仍是普通後端問題。把錯誤也設計成穩定 JSON(code、message、retryable),模型纔可能決定是換工具還是向用戶解釋失敗。

三層可以共用一份 Schema 文檔:Intent 參數 ⊂ 工具 Arguments ⊂ HTTP body。子集關係讓測試簡單:用同一份合法 JSON 夾具,先測 API,再測 Tool,再測 Intent 適配。超集關係(HTTP 比工具多三個內部字段)也可以,但不要讓模型看見內部字段,否則它會開始「幫忙填寫」你並不想公開的鍵。

接到 HTTP API 與 MCP

App 內工具打 REST 時,建議顯式編碼,不要把 GeneratedContent 直接當 Data 發出去。先映射到你的 Codable 模型,再JSONEncoder。這樣枚舉原始值、日期格式、鍵策略(snake_case)都由你控制。模型生成的是領域值,編碼器負責協議細節。

{
  "tool": "markInvoicePaid",
  "arguments": {
    "invoiceId": "inv_9f2",
    "paidAt": "2026-08-20T09:00:00Z"
  }
}

MCP 把「工具名 + 參數對象」變成 JSON-RPC。對 Apple 開發者來說,這只是第三種適配:同一markInvoicePaid(invoiceId:paidAt:),App Intent 走 perform,Foundation Models 走 Tool.call,MCP 走 tools/call。不要爲 MCP 另寫一套業務邏輯。外部 Agent 更容易把參數發錯類型(數字變成字符串),所以服務端仍要校驗,不能因爲「已經是 JSON」就信任。

隱私邊界也要寫進工具描述。On-device 模型可以讀日曆,不代表你該把日曆事件 POST 到自己的服務器。工具輸出給模型看可以是本地摘要;只有用戶明確要同步時,才構造上傳用的 JSON。系統 Agent 與 App 內 Agent 的數據出境策略應分開評審,日誌裏也要能區分來源。

落地時怎麼看 JSON

聯調 Agent 時,最有效的不是再寫一句提示詞,而是把三次快照放在一起看:模型生成的參數 JSON、你發出的 HTTP JSON、服務端回的 JSON。三次形狀不一致,問題幾乎總在映射層,而不是「模型不夠聰明」。本地把它們格式化、校驗、對比,比在 Xcode 控制檯裏盯 Optional 更快。

一份參數可以先在瀏覽器裏走一遍:JSON 格式化確認能 parse;JSON 校驗抓住尾逗號和錯誤類型;用JSON Schema把 Intent / Tool / API 的共用字段釘死;再用JSON Diff對比「模型輸出」和「實際請求體」。雲端模型的 Structured Output 做法不同,但「先契約後解析」是一樣的,可對照站內Gemini API 如何輸出 JSON。

建議固定三個夾具文件:tool-args.valid.json、http-body.valid.json、http-error.json。CI 裏用同一份 Schema 校驗前兩份;錯誤夾具用來測 Agent 是否向用戶說出可行動的失敗原因,而不是吞掉retryable: true。

常見問題 FAQ

App Intents 和 Foundation Models 的 Tool 能共用同一套參數嗎?

可以共用領域模型,但不要假設系統能直接跑你的 Tool 協議。Intent 面向系統發現與權限;Tool 面向當前會話的 prompt。中間做一層映射,JSON 夾具測的是領域模型,不是某一個框架類型。

爲什麼不讓模型直接輸出完整 HTTP 請求?

URL、Header、簽名不該由模型編造。模型只填業務字段,客戶端按固定模板發請求。否則一次幻覺就會打到錯誤環境或帶上錯誤鑑權。

JSON 和 @Generable 衝突嗎?

不衝突。@Generable 是 Swift 側的生成與解碼;JSON 是跨語言、跨網絡的形狀。先有穩定字段表,再分別生成 Swift 宏和 JSON Schema。

工具返回值一定要是 JSON 嗎?

不一定。給模型看的可以是短文本摘要,給服務端的必須是 JSON。兩套輸出不要混在同一個字符串裏硬 parse。

總結與下一步

Apple 棧上的 Agent 不是一個插座,而是三條可組合的線:系統 Agent 用 App Intents 發現並調用 App;App 內模型用 Tool 調用你的代碼;外部主機用 JSON 調用同一領域服務。JSON 負責讓參數、Schema 和 API 載荷說同一種形狀。類型安全解決編譯期,Schema 解決運行期,業務校驗解決對錯。

下一步:列出三到五個領域動作,爲每個動作寫一份最小 JSON 對象和一份 Schema,再決定它出現在 Intent、Tool 還是 HTTP。形狀穩定之後,再加提示詞和多工具編排。