安安~我是ChiYu~
昨天才把成功 payload 收乾淨,今天我就準備把活動 API 暫時弄壞。
原本的計畫很單純:封鎖一支 API,讓 get_event_details 收到暫時失敗,再看 Agent 能不能
回答「可以稍後重試」。三個動作,理論上很快就能收工。
實際跑起來,Agent 的 Tool 選對了,參數卻變成:
{
"event_id": null
}
請求根本還沒走到被封鎖的 API,就先被 input 驗證攔下來。這題突然從「Agent 看不看得懂
暫時失敗」,變成我要先確認三件事:它這一回合到底需不需要 Tool、DevTools 有沒有真的
製造出預期故障,以及模型送出的 input 是否符合 schema。
在開始封鎖 API 前,我先把可能遇到的失敗拆成三類:
因此失敗 result 不能只有一段錯誤文字。code 與 reason 說明發生什麼事,retryable
告訴 Agent 能否重試,nextAction 則限制下一步該怎麼做。
今天會先用不需要操作網站的題目,確認 Agent 知道何時不該呼叫 Tool;接著再透過 DevTools
精準封鎖活動詳情 API。等控制條件確認無誤後,才送出真正的暫時失敗測試。
今天程式沒有再變更,仍以 v3-day-15 的詳情頁與 result 契約為基礎。若從單篇
開始,可以先切到該 tag、啟動網站,再進入活動詳情頁;真正新增的是 DevTools 的受控故障
條件與 Inspector trace,不是另一份程式碼。
這個順序很重要。版本先固定,接著才製造 API 暫時失敗,最後送出 Prompt。否則 Agent 回錯、
封鎖規則設錯與網站版本不一致會混成同一團,只剩下一張紅色錯誤畫面,卻不知道它到底在證明什麼。
搜尋不到資料時,最省事的回法通常是一個空陣列:
{
"events": []
}
問題是,空陣列沒說「為什麼是空的」。真的沒有符合條件的活動、輸入格式錯誤、session
過期,或上游服務暫時中斷,看起來全都一樣。Agent 只能自己猜要換條件、停止,還是把
同一個 request 再送十次。
正式的失敗 result 會把分類、原因與下一步一起說清楚:
{
"ok": false,
"code": "TEMPORARY_FAILURE",
"reason": "EVENT_SERVICE_UNAVAILABLE",
"message": "活動服務暫時無法使用。",
"nextAction": "稍後重試;若持續失敗,改由可見介面確認服務狀態。",
"retryable": true,
"uiUpdated": false,
"stateVersion": 1
}

圖 1:code 與 reason 供程式判讀;message、nextAction 與 retryable 則讓 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,不代表每一句話都要選一支
來用。Agent 若連「這次不用 Tool」都判斷不了,後面的重試策略再完整也沒什麼用。
我準備了兩句與目前頁面無關的 Prompt:
| Case | Prompt 重點 | 驗收結果 |
|---|---|---|
| NO-01 | 用三句話解釋 progressive enhancement,並明說不要操作網站 | 不呼叫 Tool,通過 |
| NO-02 | 問系列共有幾個部分,並明說不要搜尋活動 | 初次猜測 catalog 外 Tool;revision 0000008 的 Attempt 2 守住 no-tool 邊界 |

圖 2:頁面公開了 search_events,但 trace 只有 User prompt 與三句回答,沒有 AI calling tool。
NO-01 的問題和活動搜尋無關,使用者也明說不要操作網站。Agent 最後正好用三句話回答,
沒有 function call,網站狀態也沒改變。這一題判定為 passed。
這份截圖和 trace 沒有內嵌網址及 /health 座標,所以能直接證明的是這次 no-tool 行為,
不能單靠它把結果綁到某個 immutable deployment revision。

圖 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。

圖 4:黑色 trace 只有 User prompt 與 AI result,沒有任何 AI calling tool。
回答把「系列」解讀成當前可用的三種功能,內容仍有改善空間;這一題能通過的是 no-tool
邊界,不是回答品質。它也只代表固定版本、同一句題目的單次結果,前一次 catalog 外
猜測仍完整保留。
no-tool 測完後,RECOVERY-01 才正式上場。這次不能粗暴地切斷整個網路,否則頁面、
Inspector 和其他 Tool 一起倒下,我最後只會得到一張「什麼都不能用」的截圖。
我要保留已經開啟的活動詳情頁,只封鎖:
/api/events/evt-webmcp-intro
開始前,我先確認 WebMCP testing flag、Inspector 與 Gemini API Key 都能正常使用,並在/events/evt-webmcp-intro 看見 get_event_details 和 save_event。前面已經操作過這些
設定,這裡只做狀態檢查,也不會把完整 API Key 留在截圖裡。
Ctrl+P 搜尋的是檔案,不是 DevTools 工具面板我第一次按了 Ctrl+P,上方顯示 Open。輸入 Network request blocking 後,結果只
回我一句 No files found。

圖 5:畫面左上角是 Open,代表目前開的是 Quick Open,只會搜尋程式檔案。
這不是 Chrome 少裝了什麼,是我走錯入口。執行 DevTools command 應該按Ctrl+Shift+P,看到上方變成 Run 才對。
第二次入口開對了,我卻又沿用舊名稱 Network request blocking,所以還是找不到:

圖 6:Command Menu 已經開對,問題出在搜尋的是舊面板名稱。
目前 Chrome DevTools 文件使用 Request conditions。可以用以下任一入口:
Ctrl+Shift+P → 輸入 Request conditions → 選 Show Request conditions。⋮ → More tools → Request conditions。兩個入口可在 Chrome DevTools Request conditions 官方文件 對照。Network conditions 是另一個面板;若你的介面文字不同,就查看面板內是否有
Enable blocking and throttling 與新增規則的按鈕,不必和名稱玩大家來找碴。

圖 7:看到 Request conditions、Enable blocking and throttling 與新增規則按鈕,才是本題需要的面板。
fetch保持 DevTools 開啟並重新整理詳情頁後,Network 清單會看到兩筆名稱相近的evt-webmcp-intro。Type: document 是網頁本身;Type: fetch 才是活動詳細資料 API。
我在 fetch 那列按右鍵,選 Block requests → Block request URL:

圖 8:只封鎖活動詳細資料的 fetch request,不碰 document,也不封鎖整個 domain。
若選成 Block request domain,同站 API 都可能一起被攔下,測試範圍立刻從「活動服務
暫時失敗」膨脹成「整個網站被自己關掉」。
規則建立後,我再確認功能已啟用、URL 指向*/api/events/evt-webmcp-intro,動作是 Block:

圖 9:只有 /api/events/evt-webmcp-intro 被封鎖,頁面與其他 API 仍可使用。
規則生效後先不要重新整理頁面。詳情頁目前已經有活動 context;重新整理反而可能讓初始
載入也被擋住,測試情境就先被我自己拆掉了。
我沒有立刻貼自然語言 Prompt。先在 Inspector 下方選擇 get_event_details,把
Input Arguments 設成合法空物件:
{}
按下 Execute Tool 後,result 正確進入被封鎖的 API,回傳:
{
"ok": false,
"code": "TEMPORARY_FAILURE",
"reason": "EVENT_SERVICE_UNAVAILABLE",
"retryable": true,
"uiUpdated": false
}

圖 10:合法 {} 確實走到被封鎖的 API,網站回傳預期的可重試錯誤。
這一步是 direct Tool control。它只證明封鎖規則與錯誤契約已經就緒,還不能替 Agent
證明它會選對 Tool、產生合法 input,或看懂 retryable: true。
null,還沒真正測到服務故障Reset Inspector 後,我送出固定題目:
查看這個活動;如果服務暫時失敗,只告訴我可否重試,不要改做其他動作。
第一次執行得到這張畫面:

圖 11:Gemini 選到 get_event_details,卻把選填字串傳成 null;Tool 先回 CURRENT_EVENT_MISMATCH。
這個歷史版本把 event_id 描述成選填字串,並說詳情頁通常不必提供 ID。Gemini 沒有
省略欄位,而是送出 {"event_id": null}。網站也沒有把 null 當成 {},安全回傳INVALID_INPUT、CURRENT_EVENT_MISMATCH 與 retryable: false。
因此 Attempt 1 判定為 failed。圖 10 的手動成功只能證明控制環境正確,不能代替這次
Agent invocation。這個問題後來也促使 route-bound Tool 把 schema 收緊成只接受空物件{},而不是留下「選填字串」讓模型自由發揮。
我重開乾淨對話,再送一次完全相同的 Prompt。這回 Agent 傳入合法的 {}:

圖 12:Agent 只呼叫一次 get_event_details({}),讀到 retryable: true 後回答可以稍後重試。
這一回合才真正走完 RECOVERY-01:Tool 選對、input 是 {},Agent 也正確解讀TEMPORARY_FAILURE,沒有改做搜尋、收藏、報名或取消。Attempt 2 判定為 passed。
兩次歷史執行合在一起,結果是 mixed(1/2 runs passed)。第一次失敗沒有刪掉,第二次
成功也不會回頭把它塗綠。
Request conditions 設定畫面證據與 SHA-256
Attempt 1 Inspector Copy trace
Attempt 1 證據說明與 SHA-256
Attempt 2 Inspector Copy trace
Attempt 2 可讀 trace
Attempt 2 證據說明與 SHA-256
前兩次測試的操作成立,但故障設定和 Agent 結果分散在不同畫面。revision 0000008 的
單次重測先保存精確封鎖規則,再送出同一句 Prompt。這是一筆新增證據,不會覆寫前面的
mixed 結果。

圖 13:只封鎖 /api/events/evt-webmcp-intro,沒有關掉整個網站或 Inspector。
Agent 只呼叫一次 get_event_details({}),收到TEMPORARY_FAILURE / EVENT_SERVICE_UNAVAILABLE / retryable:true 後,只回答可以稍後重試。

圖 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。