iT邦幫忙

2026 iThome 鐵人賽

DAY 14
0
Modern Web

WebMCP:30 天打造 AI Agent 看得懂、也操作得動的網站系列 第 14

Day 14|AI 加入收藏兩次怎麼辦?Stateful Tool 最容易踩的 4 個坑

  • 分享至 

  • xImage
  •  

本篇重點

Read Tool 最差通常只是查不到資料;Stateful Tool 會真的改變網站。當 Agent 可以 add_to_favoritesadd_to_cartupdate_profile,你要開始處理:重複呼叫、UI 同步、Session、競態條件。

今天用「收藏商品」做例子,讓 Tool 重複執行也不會產生錯亂。

第一個坑:Action 被重複呼叫

很直覺的實作:

async function addFavorite(productId) {
  favorites.push(productId);
}

呼叫兩次:

await addFavorite(123);
await addFavorite(123);

結果:

[123, 123]

這就是典型的重複副作用。

Browser Agent 可能因為:

  • timeout 不確定有沒有成功。
  • Agent 自己重試。
  • 網路斷線。
  • UI 沒即時更新。

再次執行同一個 Action。

Idempotency:同一動作重做結果不變

收藏這種 Action 很適合設計成 idempotent:

async function addFavorite(productId) {
  if (favorites.includes(productId)) {
    return {
      changed: false,
      message: 'Product is already in favorites.'
    };
  }

  favorites.push(productId);

  return {
    changed: true,
    message: 'Product added to favorites.'
  };
}

Tool:

await document.modelContext.registerTool({
  name: 'add_to_favorites',
  description: 'Add a product to the signed-in user favorites. Calling it for an existing favorite does not create a duplicate.',
  inputSchema: {
    type: 'object',
    properties: {
      productId: {
        type: 'integer',
        minimum: 1
      }
    },
    required: ['productId']
  },
  annotations: {
    readOnlyHint: false
  },
  execute: async ({ productId }) => {
    return JSON.stringify(await addFavorite(productId));
  }
});

用辦公室鍵盤(ID 123)來看這個流程。剛開啟 Demo 時,商品還沒被收藏,清單是空的,收藏數量為 0。

這個 Demo 先用本地記憶體保存收藏,重新整理就會清空,方便觀察每次操作帶來的變化;正式網站的資料保存與會員權限,仍要由後端處理。

📸 圖片 1|加入前,收藏數量為 0
https://ithelp.ithome.com.tw/upload/images/20260923/20121296hIV2RO0s8D.png

第二個坑:Tool 成功,但 UI 沒更新

Agent 呼叫 API 後:

Server:已收藏
Agent:已收藏
UI:♡ 還是空心

人類會以為沒成功,再點一次。

所以網站狀態要有單一來源,或至少在 Action 後同步:

async function addFavorite(productId) {
  const result = await api.addFavorite(productId);

  favoritesStore.replace(result.favorites);
  renderFavorites();

  return result;
}

以 Demo 的 add_to_favorites 為例,在 Inspector 傳入商品 ID:

{"productId":123}

執行後,鍵盤變成「已收藏」,清單多了一筆商品,收藏數量也變成 1。Tool 同時回傳 changed: trueisFavorite: truefavoritesCount: 1

這樣使用者看到的畫面,才會和 Tool 回傳的結果一致。Demo 共用本地收藏狀態;上面的 API 範例則示範正式網站如何用伺服器結果更新畫面。

📸 圖片 2|加入後,收藏數量為 1
https://ithelp.ithome.com.tw/upload/images/20260923/20121296wpEM7Mbg4w.png

重複呼叫成功,不代表又新增一筆

接著對同一個商品再呼叫一次 add_to_favorites。此時商品已經在收藏裡,所以不需要再次新增。

第二次結果會是 changed: false,但 isFavorite 仍為 truefavoritesCount 也維持 1,並回傳 Product is already in favorites.

這裡的 changed: false 並不是失敗,而是「使用者要的狀態已經存在」。即使呼叫重做,清單也不會變成兩筆相同商品。這就是前面提到的 idempotency。

📸 圖片 3|重複加入,收藏仍只有一筆
https://ithelp.ithome.com.tw/upload/images/20260923/20121296OVYN73VS7s.png

移除收藏,也要回傳變更後的狀態

反向操作也是同樣的道理。Demo 提供 remove_from_favorites,傳入相同的商品 ID:

{"productId":123}

移除後,鍵盤恢復「未收藏」,清單回到空白。Tool 回傳 changed: trueisFavorite: falsefavoritesCount: 0,讓呼叫端知道商品確實已經不在收藏裡。

如果再移除一次,結果會是 changed: false,收藏數量仍為 0。加入與移除都以「達到指定狀態」為目標,重複執行也不會多做一次副作用。

📸 圖片 4|執行 remove_from_favorites,收藏數量回到 0
https://ithelp.ithome.com.tw/upload/images/20260923/20121296tCTbtScwy6.png

加入、重複加入與移除,都應該沿用同一套狀態更新邏輯。接上真正的後端後,也要把伺服器確認的結果同步到 UI,並回傳給 Agent,避免三邊各自保留不同的狀態。

第三個坑:Session 不是 Agent 自己的

WebMCP Tool 在目前頁面執行,通常使用的是目前 Web App 的登入/Session Context。

這表示:

Agent 代表的是「現在這個使用者」,不是一個後門管理員。

後端 Action 仍要檢查:

目前使用者是否登入?
是否擁有操作權限?
商品是否存在?
這個狀態是否允許修改?

不要因為 request 是從 WebMCP 進來就跳過權限。

第四個坑:Race Condition

兩個 Action 幾乎同時:

add_to_favorites(123)
remove_from_favorites(123)

最後狀態取決於 Server 的執行順序。

因此 Result 應該回「執行後的真實狀態」,而不是只回:

OK

例如:

{
  "status": "success",
  "productId": 123,
  "isFavorite": false,
  "favoritesCount": 8
}

Agent 才知道現在到底是什麼狀態。

Action Tool 的 Result 我會多回一點狀態

Read Tool 可以很精簡;Write Tool 我通常希望回:

有沒有變更
變更後狀態
必要的識別 ID
下一步需要什麼

例如:

{
  "status": "success",
  "changed": true,
  "productId": 123,
  "isFavorite": true
}

Stateful 不代表 consequential

「加入收藏」有副作用,但通常風險不高、可逆。

「刪除帳號」也是 Stateful,但風險完全不同。

所以不能只用:

Read / Write

兩類就結束。

可帶走的重點

  1. Stateful Tool 要預期 Agent 可能重試。
  2. 能設計成 idempotent 的 Action 儘量設計成 idempotent。
  3. 執行後同步 Server / UI / Agent 狀態。
  4. Result 應回變更後狀態,不只回 OK
  5. WebMCP 不會替你處理 Authentication/Authorization/Race Condition。

上一篇
Day 13|「幫我填這張表」其實很難:WebMCP Form 從填值、驗證到送出
系列文
WebMCP:30 天打造 AI Agent 看得懂、也操作得動的網站14
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言