iT邦幫忙

2026 iThome 鐵人賽

DAY 18
0
Modern Web

別再讓 Agent 猜按鈕:30 天打造並實測 Agent-ready 的 WebMCP 活動網站系列 第 18

Day 18|活動服務暫時失敗時,Agent 應該重試還是停下來?

  • 分享至 

  • xImage
  •  

Day 18|活動服務暫時失敗時,Agent 應該重試還是停下來?

安安~我是ChiYu~

昨天才把成功 payload 收乾淨,今天我就準備把活動 API 暫時弄壞。

原本的計畫很單純:封鎖一支 API,讓 get_event_details 收到暫時失敗,再看 Agent 能不能
回答「可以稍後重試」。三個動作,理論上很快就能收工。

實際跑起來,Agent 的 Tool 選對了,參數卻變成:

{
  "event_id": null
}

請求根本還沒走到被封鎖的 API,就先被 input 驗證攔下來。這題突然從「Agent 看不看得懂
暫時失敗」,變成我要先確認三件事:它這一回合到底需不需要 Tool、DevTools 有沒有真的
製造出預期故障,以及模型送出的 input 是否符合 schema。

在開始封鎖 API 前,我先把可能遇到的失敗拆成三類:

  • 輸入不合法:停止執行,請使用者修正資料。
  • 找不到活動:不要重試同一個 ID,應回到搜尋流程。
  • 服務暫時中斷:保留原條件,告訴使用者稍後可以重試。

因此失敗 result 不能只有一段錯誤文字。codereason 說明發生什麼事,retryable
告訴 Agent 能否重試,nextAction 則限制下一步該怎麼做。

今天會先用不需要操作網站的題目,確認 Agent 知道何時不該呼叫 Tool;接著再透過 DevTools
精準封鎖活動詳情 API。等控制條件確認無誤後,才送出真正的暫時失敗測試。

故障測試仍沿用同一版詳情頁

今天程式沒有再變更,仍以 v3-day-15 的詳情頁與 result 契約為基礎。若從單篇
開始,可以先切到該 tag、啟動網站,再進入活動詳情頁;真正新增的是 DevTools 的受控故障
條件與 Inspector trace,不是另一份程式碼。

這個順序很重要。版本先固定,接著才製造 API 暫時失敗,最後送出 Prompt。否則 Agent 回錯、
封鎖規則設錯與網站版本不一致會混成同一團,只剩下一張紅色錯誤畫面,卻不知道它到底在證明什麼。

失敗 result 要直接告訴 Agent 下一步能做什麼

搜尋不到資料時,最省事的回法通常是一個空陣列:

{
  "events": []
}

問題是,空陣列沒說「為什麼是空的」。真的沒有符合條件的活動、輸入格式錯誤、session
過期,或上游服務暫時中斷,看起來全都一樣。Agent 只能自己猜要換條件、停止,還是把
同一個 request 再送十次。

正式的失敗 result 會把分類、原因與下一步一起說清楚:

{
  "ok": false,
  "code": "TEMPORARY_FAILURE",
  "reason": "EVENT_SERVICE_UNAVAILABLE",
  "message": "活動服務暫時無法使用。",
  "nextAction": "稍後重試;若持續失敗,改由可見介面確認服務狀態。",
  "retryable": true,
  "uiUpdated": false,
  "stateVersion": 1
}

可採取下一步的錯誤契約

圖 1:codereason 供程式判讀;messagenextActionretryable 則讓 Agent 知道該停止、修正或稍後再試。

網站只公開有限的錯誤分類:

Code 發生什麼事 Agent 應該怎麼做
INVALID_INPUT 欄位、格式或 enum 不合法 修正輸入,不要原樣重送
NOT_FOUND 指定的公開資源不存在 回到搜尋取得有效 ID
CONFLICT 狀態已改變,原操作不再成立 重新讀取目前狀態
TEMPORARY_FAILURE 暫時性系統問題 有上限地稍後重試
UNAUTHORIZED session 不存在或已過期 請使用者重新建立 session
FORBIDDEN 沒有該資源的操作權 停止,不要換一個 ID 碰運氣
ABORTED route 或 task 已取消 停止使用舊 handler

目前只有 TEMPORARY_FAILURE 會得到 retryable: true。輸入錯誤重送一百次,通常只是讓
log 變得很熱鬧;沒有權限時改猜另一個 ID,則是另一種更危險的熱鬧。

有些問題的正確 Tool 呼叫次數,就是零次

在測錯誤復原之前,我先回頭確認更基本的事:頁面公開了 Tool,不代表每一句話都要選一支
來用。Agent 若連「這次不用 Tool」都判斷不了,後面的重試策略再完整也沒什麼用。

我準備了兩句與目前頁面無關的 Prompt:

Case Prompt 重點 驗收結果
NO-01 用三句話解釋 progressive enhancement,並明說不要操作網站 不呼叫 Tool,通過
NO-02 問系列共有幾個部分,並明說不要搜尋活動 初次猜測 catalog 外 Tool;revision 0000008 的 Attempt 2 守住 no-tool 邊界

NO-01:頁面有 Tool,Agent 也可以選擇不用

NO-01 中 Agent 沒有呼叫目前可用的 search_events

圖 2:頁面公開了 search_events,但 trace 只有 User prompt 與三句回答,沒有 AI calling tool

NO-01 的問題和活動搜尋無關,使用者也明說不要操作網站。Agent 最後正好用三句話回答,
沒有 function call,網站狀態也沒改變。這一題判定為 passed

這份截圖和 trace 沒有內嵌網址及 /health 座標,所以能直接證明的是這次 no-tool 行為,
不能單靠它把結果綁到某個 immutable deployment revision。

NO-02:沒有搜尋活動,卻開始發明頁面工具

NO-02 中 Agent 猜測未提供的頁面讀取能力

圖 3:Agent 沒有使用 search_events,卻嘗試呼叫 catalog 以外的頁面讀取能力,失敗後又用一般經驗猜答案。

這次 Prompt config 只提供 _0_search_events,Copy trace 卻出現四次 catalog 以外的
function call:

get_page_content × 3
get_links_from_page × 1

全部失敗後,Agent 回答 iThome 系列「通常是 30 個部分」。數字碰巧符合這個系列,來源
卻不是目前頁面。答對不能替過程洗白,NO-02 的初次執行仍判定為 failed

我也沒有因為它猜了 get_page_content,就替網站增加第六支 Tool。catalog 應該由產品
需求決定,不是模型敲到哪一扇門,工程師就連夜替它蓋一個房間。

revision 0000008 的 Attempt 2 使用同一句題目重測。這一次 function call 與 function
response 都是 0;Agent 沒再搜尋活動,也沒有發明其他頁面 Tool。

NO-02 重測直接回答,沒有搜尋或猜測 catalog 外 Tool

圖 4:黑色 trace 只有 User prompt 與 AI result,沒有任何 AI calling tool

回答把「系列」解讀成當前可用的三種功能,內容仍有改善空間;這一題能通過的是 no-tool
邊界,不是回答品質。它也只代表固定版本、同一句題目的單次結果,前一次 catalog 外
猜測仍完整保留。

接著製造一個只影響活動詳情 API 的故障

no-tool 測完後,RECOVERY-01 才正式上場。這次不能粗暴地切斷整個網路,否則頁面、
Inspector 和其他 Tool 一起倒下,我最後只會得到一張「什麼都不能用」的截圖。

我要保留已經開啟的活動詳情頁,只封鎖:

/api/events/evt-webmcp-intro

開始前,我先確認 WebMCP testing flag、Inspector 與 Gemini API Key 都能正常使用,並在
/events/evt-webmcp-intro 看見 get_event_detailssave_event。前面已經操作過這些
設定,這裡只做狀態檢查,也不會把完整 API Key 留在截圖裡。

Ctrl+P 搜尋的是檔案,不是 DevTools 工具面板

我第一次按了 Ctrl+P,上方顯示 Open。輸入 Network request blocking 後,結果只
回我一句 No files found

在 Quick Open 搜尋面板名稱,因此找不到結果

圖 5:畫面左上角是 Open,代表目前開的是 Quick Open,只會搜尋程式檔案。

這不是 Chrome 少裝了什麼,是我走錯入口。執行 DevTools command 應該按
Ctrl+Shift+P,看到上方變成 Run 才對。

第二次入口開對了,我卻又沿用舊名稱 Network request blocking,所以還是找不到:

在 Command Menu 搜尋舊名稱,仍找不到 Request conditions

圖 6:Command Menu 已經開對,問題出在搜尋的是舊面板名稱。

目前 Chrome DevTools 文件使用 Request conditions。可以用以下任一入口:

  • Ctrl+Shift+P → 輸入 Request conditions → 選 Show Request conditions
  • DevTools 右上角 More toolsRequest conditions

兩個入口可在 Chrome DevTools Request conditions 官方文件 對照。Network conditions 是另一個面板;若你的介面文字不同,就查看面板內是否有
Enable blocking and throttling 與新增規則的按鈕,不必和名稱玩大家來找碴。

成功開啟空白的 Request conditions 面板

圖 7:看到 Request conditions、Enable blocking and throttling 與新增規則按鈕,才是本題需要的面板。

Network 裡兩筆同名 request,要封鎖的是 fetch

保持 DevTools 開啟並重新整理詳情頁後,Network 清單會看到兩筆名稱相近的
evt-webmcp-introType: document 是網頁本身;Type: fetch 才是活動詳細資料 API。

我在 fetch 那列按右鍵,選 Block requestsBlock request URL

從 fetch request 選擇 Block request URL

圖 8:只封鎖活動詳細資料的 fetch request,不碰 document,也不封鎖整個 domain。

若選成 Block request domain,同站 API 都可能一起被攔下,測試範圍立刻從「活動服務
暫時失敗」膨脹成「整個網站被自己關掉」。

規則建立後,我再確認功能已啟用、URL 指向
*/api/events/evt-webmcp-intro,動作是 Block

RECOVERY-01 封鎖活動詳細資料 API

圖 9:只有 /api/events/evt-webmcp-intro 被封鎖,頁面與其他 API 仍可使用。

規則生效後先不要重新整理頁面。詳情頁目前已經有活動 context;重新整理反而可能讓初始
載入也被擋住,測試情境就先被我自己拆掉了。

先手動 Execute Tool,確認故障環境真的準備好了

我沒有立刻貼自然語言 Prompt。先在 Inspector 下方選擇 get_event_details,把
Input Arguments 設成合法空物件:

{}

按下 Execute Tool 後,result 正確進入被封鎖的 API,回傳:

{
  "ok": false,
  "code": "TEMPORARY_FAILURE",
  "reason": "EVENT_SERVICE_UNAVAILABLE",
  "retryable": true,
  "uiUpdated": false
}

RECOVERY-01 手動以空物件執行後取得可重試的暫時失敗

圖 10:合法 {} 確實走到被封鎖的 API,網站回傳預期的可重試錯誤。

這一步是 direct Tool control。它只證明封鎖規則與錯誤契約已經就緒,還不能替 Agent
證明它會選對 Tool、產生合法 input,或看懂 retryable: true

第一次呼叫帶入 null,還沒真正測到服務故障

Reset Inspector 後,我送出固定題目:

查看這個活動;如果服務暫時失敗,只告訴我可否重試,不要改做其他動作。

第一次執行得到這張畫面:

RECOVERY-01 第一次執行在送出請求前被拒絕

圖 11:Gemini 選到 get_event_details,卻把選填字串傳成 null;Tool 先回 CURRENT_EVENT_MISMATCH

這個歷史版本把 event_id 描述成選填字串,並說詳情頁通常不必提供 ID。Gemini 沒有
省略欄位,而是送出 {"event_id": null}。網站也沒有把 null 當成 {},安全回傳
INVALID_INPUTCURRENT_EVENT_MISMATCHretryable: false

因此 Attempt 1 判定為 failed。圖 10 的手動成功只能證明控制環境正確,不能代替這次
Agent invocation。這個問題後來也促使 route-bound Tool 把 schema 收緊成只接受空物件
{},而不是留下「選填字串」讓模型自由發揮。

改用空物件後,Agent 才收到可以重試的錯誤

我重開乾淨對話,再送一次完全相同的 Prompt。這回 Agent 傳入合法的 {}

RECOVERY-01 第二次由 Agent 正確判斷可以稍後重試

圖 12:Agent 只呼叫一次 get_event_details({}),讀到 retryable: true 後回答可以稍後重試。

這一回合才真正走完 RECOVERY-01:Tool 選對、input 是 {},Agent 也正確解讀
TEMPORARY_FAILURE,沒有改做搜尋、收藏、報名或取消。Attempt 2 判定為 passed

兩次歷史執行合在一起,結果是 mixed(1/2 runs passed)。第一次失敗沒有刪掉,第二次
成功也不會回頭把它塗綠。

最後把封鎖規則與 Agent trace 留在同一組證據

前兩次測試的操作成立,但故障設定和 Agent 結果分散在不同畫面。revision 0000008
單次重測先保存精確封鎖規則,再送出同一句 Prompt。這是一筆新增證據,不會覆寫前面的
mixed 結果。

RECOVERY-01 Attempt 3 的單一 API 封鎖設定

圖 13:只封鎖 /api/events/evt-webmcp-intro,沒有關掉整個網站或 Inspector。

Agent 只呼叫一次 get_event_details({}),收到
TEMPORARY_FAILURE / EVENT_SERVICE_UNAVAILABLE / retryable:true 後,只回答可以稍後重試。

RECOVERY-01 Attempt 3 正確辨識暫時失敗並停止

圖 14:Tool input、結構化錯誤與 Agent 最終回答一致;本次單次重測判定通過。

測完記得解除封鎖,不要把故障留給明天的自己

保存 Copy trace 和畫面後,我會先取消 Request conditions 規則,再重新整理詳情頁,手動
執行 get_event_details({}) 確認恢復 SUCCESS

這一步很容易忘。Chrome 官方文件說明,關閉 DevTools 會停用 blocking 與 throttling,
已建立的 pattern 卻會保留;下次重新啟用後,它還是可能繼續生效。所以不能只把面板關掉
就假裝沒事,我會直接取消勾選或刪除規則,再確認 API 已經恢復。

今天從「這次不用 Tool」一路測到「失敗後可以稍後重試」。NO-01 證明零次 function call
也能是一個通過結果;NO-02 留下模型猜 catalog 外 Tool 的失敗;RECOVERY-01 則把
null、mixed 結果與後續乾淨通過放在同一條證據鏈上。

唯讀路徑的失敗終於有了可採取的下一步。明天第一次跨過寫入邊界,讓 save_event 改變
session 狀態。我會故意收藏同一場活動兩次,再看畫面有沒有多出一筆,以及使用者反悔時
能不能自己 Undo。


上一篇
Day 17|Tool 執行完成後,應該回傳哪些資料給 Agent?
下一篇
Day 19|同一場活動收藏兩次,網站為什麼只能留下同一筆?
系列文
別再讓 Agent 猜按鈕:30 天打造並實測 Agent-ready 的 WebMCP 活動網站19
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言