AI 開發

OpenAI Agents API + JSON Schema 實戰:如何讓 AI Agent 輸出穩定、可驗證、可執行的 JSON 資料?

閱讀約 16 分鐘

通用 Schema 文已經寫過「為什麼要契約」。本篇只落到 Agents API:模型填的每一段 JSON,都要先過門,再執行。工具參數、MCP 輸入、你自己的帳簿,是三道門,不是一句「請輸出 JSON」。

閱讀前速覽

門 鎖什麼 誰執行
Function parameters order_id、布林、列舉 additionalProperties: false
宿主校验 required_actions 裡的 arguments 不過門就不跑函數
最终卡片 給使用者看的物件 另寫 Schema;不是 text.format
MCP 另一張工具表的 inputSchema subagent 可繼承

Schema 是門,不是禮貌。寬 object 等於多開一扇門。

穩定、可驗證、可執行,是三道手續,不是一個開關

在 Agents API 裡,模型仍然只填 JSON。官方 function 定義要帶一份 parameters Schema:required、additionalProperties: false、必要時 pattern / enum。這讓填充更穩。可驗證是你在執行前,用同一份 Schema 再跑一遍 arguments。可執行是過門之後,宿主才產生副作用。

  • 穩定:把工具參數寫成嚴 Schema。寬 object 等於請模型發明鍵。
  • 可驗證:required_actions 裡的呼叫也要本地校驗。歷史裡的 function_call 不夠。
  • 可執行:不過門就不跑 lookup_order。回寫 success: false 和原因,不要默默改庫。

工具怎麼調,見 OpenAI Agents API 完整解析。四個物件怎麼用 ID 相連,見 Agents API 的 JSON 資料結構。Schema / Function Calling / Structured Output 的通用分工,見 AI 為什麼需要 JSON Schema。本篇不再重複那三個詞的定義,只寫 Agents session 上怎麼落地。

第一道門:寫在 agent.tools 裡的 parameters

官方 function 條目是 name、description、一份 JSON Schema。建立 session 時寫進 agent.tools。虛構訂單,不要貼真實金鑰:

{
  "type": "function",
  "name": "lookup_order",
  "description": "Look up an order by ID. Confirm before any refund.",
  "parameters": {
    "type": "object",
    "properties": {
      "order_id": { "type": "string", "pattern": "^[A-Z]-\\d{4}$" },
      "include_lines": { "type": "boolean" }
    },
    "required": ["order_id", "include_lines"],
    "additionalProperties": false
  }
}

要點就這幾個:所有欄位進 required;物件關掉多餘鍵;單號用 pattern,不要只寫 string。函數預設會預載。要延後載入,設 defer_loading: true,並在工具表裡放 tool_search。門本身不變。

過不去的參數,不要執行

session 停在 agent.session.requires_action 時,required_actions 裡會出現 function_call。下面這一份看起來像在查單,其實已經越門:單號不合 pattern,還多了一個 refund:

{
  "type": "function_call",
  "turn_id": "turn_2",
  "call_id": "call_3c91",
  "name": "lookup_order",
  "arguments": {
    "order_id": "1042",
    "include_lines": true,
    "refund": true
  }
}

同一對 turn_id / call_id,過門的形狀應當是這樣。多一個鍵、少一個 required、pattern 漂了,都不該進執行:

{
  "type": "function_call",
  "turn_id": "turn_2",
  "call_id": "call_3c91",
  "name": "lookup_order",
  "arguments": {
    "order_id": "A-1042",
    "include_lines": true
  }
}

第二道門:宿主用同一份 Schema 再跑

官方 Schema 提高填充品質,但不替你承擔副作用。執行前在本地再校驗一次。失敗寫成物件,便於複盤:

{
  "check": "lookup_order",
  "schema": "parameters",
  "pass": ["order_id", "include_lines"],
  "fail": [
    { "path": "/order_id", "reason": "pattern" },
    { "path": "/refund", "reason": "additionalProperties" }
  ],
  "execute": false
}

失敗就向 events 端點回 agent.session.input.tool_result,success: false,error 寫人能讀的原因。成功才序列化 output 字串。不要新開 session「重試」。

{
  "type": "agent.session.input.tool_result",
  "turn_id": "turn_2",
  "call_id": "call_3c91",
  "success": false,
  "error": "order_id failed schema: expected ^[A-Z]-\\d{4}$"
}

最終卡片是第三道門,不是 Responses 的 text.format

給使用者看的訂單卡片,和工具參數不是同一份 Schema。Agents API 的 session 不會自動套上 Responses 那種 text.format.json_schema。三件事要分開寫:

  • 工具參數:鎖副作用入口。查單、改庫走這裡。
  • 最終卡片:你從 items 抽出物件後,用另一份 Schema 校驗再展示。
  • 若必須用 Structured Outputs 鎖最終形狀:那是 Responses 的層。不要混進同一條 session 資源。

循環該不該託管,見 Agents API vs Responses API。MCP 那一行只是另一張工具表,inputSchema 同樣要 additionalProperties: false。邊界見 MCP Tool Schema vs Function Calling。subagent 繼承 MCP Schema,不繼承你的 function。

五步把 JSON 收成可執行

不要從「請輸出 JSON」開始。按門走:

  1. 先寫工具 Schema。pattern、enum、required、additionalProperties: false 一次收緊。
  2. 同一份 Schema 放進宿主校驗器。和官方 parameters 不要各寫各的。
  3. required_actions 到了先校驗再執行。不過門就回 success: false。
  4. 最終卡片另寫 Schema。從 items 抽出物件後校驗,不要把散文當卡片。
  5. 帳簿欄位自己存。compact 會丟細節。金額、審批不要只活在對話裡。

工作流怎麼收成 Schema,見 JSON Schema 與 AI Agent 工作流。本篇只要求:Agents session 上,每道門都有物件。

複盤穩定輸出,先攤開兩段 JSON

漂了,多半是門沒對齊,不是模型變笨。

  • 把 parameters 和 arguments 格式化,看多餘鍵和缺欄位。
  • 用 JSONPath 提取 抽出 order_id、$.parameters.required。
  • 過門失敗和成功的呼叫 JSON Diff。pattern 漂了先看 Schema,再怪模型。
  • 最終卡片 Schema 和工具 Schema 分開審。鎖卡片不鎖參數,等於只裝修門面。
  • 訂單、session、內網 MCP 不要貼進公共格式化站。JSONSort 在瀏覽器本地跑。

兩段軌跡都能按 Schema 重建,你才是在收 JSON,不是在收散文。

常見誤區

  • 只在 instructions 裡寫「請輸出 JSON」。那不是門。門在 parameters 和宿主校驗器。
  • required 只列一部分欄位。沒列的鍵,模型可以不填,下一跳就會空。
  • additionalProperties 不寫或寫成 true。官方示例關掉多餘鍵。多一個 refund,副作用就偏了。
  • 把 Responses 的 text.format 抄進 Agents session。兩套資源。最終卡片你自己校驗。
  • 校驗失敗還是執行了函數。可執行的前提是過門。先回 error。
  • 讓 subagent 跑你的 function。子代理不支援 function 工具。嚴 Schema 留在主 Agent。

FAQ

Agents API 有沒有 Structured Outputs?

工具參數靠 JSON Schema 收緊。給使用者看的最終物件,session 不會自動套 Responses 的 text.format。要鎖最終形狀,自己校驗,或把那一跳放到 Responses。

官方 Schema 還要我再校驗嗎?

要。官方門提高填充品質。副作用發生在你的行程。required_actions 裡的 arguments 過同一份 Schema,失敗就 success: false。

pattern 和 enum 有必要嗎?

有。string 太寬,模型會填 1042 而不是 A-1042。金額、狀態用 enum 或明確格式。寬類型是最常見的越門方式。

MCP 工具要不要同一套紀律?

要。inputSchema 同樣關掉多餘鍵、列全 required。subagent 繼承 MCP,不繼承 function。查自家訂單的門,留在主 Agent。

校驗失敗會不會把 session 卡死?

回同一對 turn_id / call_id 的 tool_result 即可。error 寫清楚,模型可以改參數再調。不要另開 session 當重試。

結論

Agents API 上的穩定 JSON,不是更長的提示。它是三道門:工具 parameters、宿主再校驗、最終卡片另寫 Schema。可執行發生在過門之後。

先寫清 A-1042 過不了哪一條規則。能指到路徑,資料才算可驗證。模型會換;門應該先穩定。

延伸阅读

更新紀錄: 首次發布