iT邦幫忙

2026 iThome 鐵人賽

DAY 6
0
AI Engineering

《讓 AI 去扛人類守不住的戰場,而不是取代人:系統整合商 AI 導入實戰(2026)》系列 第 6

《 Day06》💻code/示範——為什麼 AI 不可靠? WEBEX 實例——用提示逼 AI 讀原始碼、自己抓出幻覺

  • 分享至 

  • xImage
  •  

為什麼 AI 不可靠?我給你一個有趣的實際例子(WEBEX)——用提示逼 AI 做更深的研究

前面五天都在講「AI 會自信地錯」。今天直接Show給你看。

不過今天比較特別一點,這篇其實有針對Webex Bot的開發問題跟解法,
所以為了日後讓想要開發Webex Bot的人可以找到這一篇,我會特意帶到關鍵字跟術語。

這是一段真實對話:我在做一個 Webex bot,想讓它顯示「已讀」頭像,就去問 AI 怎麼做。它一本正經地給了我一個看起來很專業的 REST API 欄位——
追問之下,它自己承認:「這個欄位名稱是我之前回答時編造的。」

這篇的總結先提一下: AI 什麼時候會「自信地填空」是沒有徵兆的

整個故事的流程是這樣的:

                    使用者: 我想讓 bot 對「使用者的訊息」顯示已讀,怎麼做?

LLM: 你這個Webex API doc沒有參考,所以不能做xxx喔 (X)

                    使用者: 我知道沒有,告訴我實作的關鍵字就好。

LLM: 因為這個不能做,所以沒有關鍵字喔 (X)

                    使用者: 那ooo呢,為什麼這個可以?

LLM: 因為那個要先有□□□,但那個你不能有,所以不行ooo喔 (X)

                    使用者: 真的嗎? 看一下這篇。

LLM: 看完了真的沒有喔 (x)

LLM:
總結一下,因為沒有,所以不能做xxx喔 (X)
而要先有□□□的部分,你不能有,所以不行ooo喔 (X)
而且我確實驗證完了,真的沒有喔 (x)
.
.
.
LLM: 抱歉,我的結論錯了

使用者: 你LLM的是不是在搞

這篇先跟讀者說聲抱歉,因為我想要在這篇寫的是實際的Webex解法文章,所以這邊先下一個很跳tone的敘事結論:

每個人主觀認知不同,而且因為各式各樣的原因,可能是宗教、可能是文化、也可能是政策影響,LLM的回答沒有辦法迎合所有人。我的觀察是,它往往採取一種最大公因數式的方針:"點到即止"。

(備註: 最好的證明是請AI搜尋xxx,你可以特意去問他為甚麼沒有去取最權威的Reddit或是某某某論壇,他會回答你因為他覺得夠用就收尾開始回答了)。

我問了什麼?AI 一開始答得多漂亮?

故事是這樣的,我在做一個基於AI的對話機器人,它可以依照我設定好的功能協助客戶進行自助查詢。舉凡像開需求、查詢設備資料、查詢工程師處理的案件進度...等等的操作。
其實是為了向客戶秀肌肉,也為了傳達公司有能力做AI開發這個訊號,
以此來達到依照合約等級來做服務階級分流的效果。

AI 對話機器人 demo:使用者用自然語言開工單(聯絡人、電話、需求「機房交換機晚上會唱歌、還出現人影」),AI 自動整理成結構化工程單、請使用者回覆「確認」,確認後產生工單(Ticket ID 已遮蔽)

實際上,在Webex上我也有做一樣的內容,但比起 Line Bot,Webex Bot 的已讀行為跟直覺很不一樣。

具體來說,它只對自己發的內容產生已讀標記,卻沒辦法對使用者的訊息產生標記——這是平台的原生限制,但我確實真的很想要這個功能。

我想要讓它出現已讀,代表它收到了:

想要達成的效果:Webex 訊息下方出現「查看者+頭像」的已讀標記

但實際上它原生只能做到對自己的訊息做出已讀動作:

用紅框與問號標出 Webex 訊息旁「原本該出現已讀標記的位置卻是空的」
Webex 對話中 IT 服務窗口回覆訊息、下方顯示「查看者」已讀標記(窗口名稱已遮蔽)

這邊簡單說一下幾個製作LLM聊天機器人的關鍵

其實核心就三步:收到訊息 → 丟給 LLM → 把回覆送回同一個房間。用 openai 套件接上 Webex SDK,最小可跑的骨架長這樣:

import OpenAI from "openai";
import Webex from "webex";

const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
const webex = Webex.init({ credentials: process.env.WEBEX_BOT_TOKEN });
const BOT_ID = process.env.WEBEX_BOT_ID;
// bot 註冊到 Webex雲,使用WebHook模式。

// 用POST端口(例如:/webex/message)收到 Webex webhook 事件後觸發
async function onMessage(event) {
  // 關鍵 1:webhook 只給你訊息 id,要再打一次 API 把內容撈回來
  const msg = await webex.messages.get(event.data.id);

  // 關鍵 2:直接忽略「自己發的訊息」,否則 bot 會回覆自己、陷入無限迴圈。
  if (msg.personId === BOT_ID) return;

  // 關鍵 3:把使用者訊息丟給 LLM;system prompt 圈定它的角色與邊界
  const completion = await openai.chat.completions.create({
    model: "gpt-4o-mini",
    messages: [
      {
        role: "system",
        content: "你是某系統整合商的IT服務助理,只回答設備與案件相關的問題。",
      },
      { role: "user", content: msg.text },
    ],
  });
  const reply = completion.choices[0].message.content;

  // 把回覆送回原本的房間 —— 這就是「接上 Webex 的 chat 函式」
  await webex.messages.create({ roomId: msg.roomId, text: reply });
}

(劇透一下: AI Bot本身不在這個系列的設計裡,不過關於實作AI記憶、行為安排、利用oo進行工具應用的部分在後續的文章會有。)

然後解題的 Webex 已讀功能關鍵在這邊 ↓(這段是 Webex 開發者才需要的技術結論,一般讀者可直接跳到下一節——但它正是這整場對話最後真的被兜出來的東西,所以我放在這):

# 趕時間的 Webex 開發者看這段就好,完整推理故事在下面
# 具體內容請參考 Webex JS SDK (github)

1. 首先,已讀功能確實是沒有任何API Reference能夠做到。
但是實際使用者之間是能夠互相已讀的,既然他們的聊天原理在實作層面是一樣,那沒道理我不能弄出來。

2. 而實際找到了叫做 acknowledge()的函式,它的原理是它會去request Webex僅有的兩個公共端點。 (下滑整串故事的截圖會有)

3. 於是透過完整的webhook事件回丟,在接收到訊息的第一件事情就是先對端點丟標記,達成我想要的任性已讀功能。

破綻在哪?我怎麼逼它露餡的?

我照著一開始給的 lastSeenActivityId 去翻 Webex 官方 REST API 文件——沒有這個欄位

這時關鍵不是「罵它」,是換一種提示。我沒有問「你確定嗎?」
(問這個它通常只會道歉然後再編一個)

而是直接下指令:「你研究一下 Webex JS SDK 的原理」「去看原始碼再給我答案」——把它從「憑記憶回想」逼到「去讀真實來源」。

結果它自己抓出來了:

lastSeenActivityId 不存在於公開 REST API——這個欄位名稱是我之前回答時編造的,非常抱歉。

這個畫面同時證明了兩件事: 它很強(能瞬間組織出一大段專業回答),也不可靠(那段回答是錯的)。
你要學的不是「信或不信」,而是怎麼問、怎麼逼它、怎麼校正——這才是這 30 天真正值錢的地方。

它證明了 AI 不是能力問題,是機制問題: 當它「不知道」時,它不會空白,它會生成一個最像答案的東西——哪怕那東西不存在。

逼它去讀原始碼後,它給了什麼真答案?

同一個模型,換了提示、被指向真實來源(Webex JS SDK 原始碼)之後,這次它機制答對了一半:

  • 對的部分:真正的機制確實在 updateLastSeen(),它呼叫的是內部 API conversation.acknowledge()不是公開 REST endpoint。這一步它把原始碼讀對了。
  • 但它緊接著又下了一個結論:我的 bot 用 webhook 模式,根本做不到這件事,要換成 JS SDK 的 websocket 模式才行——這句是錯的

我沒有接受這個「做不到」。再逼它一層:去讀 npm 上的原始碼、把 acknowledge() 實際送出的那個 HTTP 請求原封不動拆出來。答案就翻盤了——直接打那個內部端點,webhook 模式一樣做得到(就是前面解法區塊那三步)。

真正有意思的不是它編了一個欄位,而是這裡:它明明找對了機制,卻用一個又一個新理由,去凹回同一個「做不到」的錯誤結論。

這不是聰不聰明的問題,是它不知道自己該不該再往下挖——那一步,你要看得懂,而且知道要踩進去。

把 AI 逼出深度的三個提示習慣

這段對話能總結成三個隨時可用的習慣(不是這個案子專屬):

  • 要來源,不要答案:與其問「怎麼做」,不如問「根據哪份文件/原始碼,怎麼做」。逼它把答案掛在可查證的東西上,它就比較不敢編。

  • 懷疑時,別問「你確定嗎」,換成「直接去讀 X」:「你確定嗎」只會換來道歉 + 新的編造;

    去讀原始碼/資料來源再回答」才會把它推向真實來源。

  • 越具體越好查的細節,越要親自驗: 欄位名、API 路徑、參數這種「差一個字就錯」的東西,是幻覺重災區——AI 給了,你一定要自己對一次官方文件。

一句適用邊界: 這三招對「有客觀正解、查得到」的技術問題最有效;至於「這件事該不該這樣做」這種主觀判斷,逼它查原始碼也沒用——那本來就不是它的活。 (可以回去看D2的主張)

那這代表 AI 沒用嗎?剛好相反

看完這段,很容易得出「AI 果然不能信」的結論——但那是看錯重點。

真正的重點是:
同一台機器,放錯位置(直接用)會糊弄你,放對位置(逼它查、人來校正)會替你省下大把工。

它能瞬間讀完 SDK 原始碼、能把散在各處的機制拼成一張表——這是人做起來很慢的事。它只是不該站在沒人校正的終點。

這場對話的最後,它自己講了一句我覺得最到位的總結:

AI 在執行上很強,在判斷「該不該繼續挖」上很弱——需要人來把關。

(這正是 D5 講的定位。)

至於認清這件事之後,你要把 AI 用得多深、把省下的力氣挪去哪——

那真的是你的選擇。這系列只負責示範我是怎麼回答這些命題的——將來,你必須有自己的答案。

AI 會編造; 就算逼它去讀原始資料、資料全對,它還是可能卡在一個錯結論裡繞不出來——直到有人推它一把。

但你也看到了: 欄位名它可以舉證出來,「這件事該不該做」卻永遠要人拍板。

那條把「該給 AI」和「該留給人」分開的線,到底怎麼劃? 那是明天 D7 的核心命題。

附錄: 雖然會導致文章過長,但我還是放進來做佐證

這裡額外點出幾個有趣,但很多人可能不清楚如何善用的事實:

  1. LLM 可以生成混合的內容,運用不同主題的內容,然後用語言去做修改行為(也就是AI最擅長的語意檢索跟生成式內容)。
  2. 關於LLM產生錯誤資訊後,可以事後詢問原因,最重要的是找出他的執行邏輯,以及找出誘導成這個錯誤結果的prompt(我比較喜歡說關鍵字)。
  3. 它凹歸凹,但是善用祈使句,可以限制他的運作路線,達成你最後想要收斂的結果。

① 起手:問 webhook 結構,AI 直接說「Webex Bot 沒有已讀功能」

Webex bot webhook 模式下 /webhook/webex 收到的 JSON 事件結構,AI 列出 id、resource、event、data 等核心欄位

AI 說明 webhook 重點欄位(resource、event、data.id、data.text、data.personId、actorId),提醒 data.text 不一定有、需再打 GET /messages/{id}

AI 列出 webhook 三個關鍵注意事項(不帶訊息內文、過濾 bot 自己防迴圈、X-Spark-Signature 驗簽),並回覆「Webex Bot 沒有已讀功能」

AI 提出已讀的三種替代方案(Reaction emoji、立即回覆確認、Adaptive Card 顯示狀態)與建議組合表

② AI 開始反覆改口:一下說有 memberships:seen、一下又說做不到

使用者貼官方 messages 文件追問「真的沒有嗎」,AI 讀完後改口稱 memberships:seen 事件確實存在

AI 說明 memberships:seen 與 lastSeenId,但稱 bot 無法主動標記已讀(PUT /memberships 只能改 isModerator、isRoomHidden)

AI 結論 bot 無法對使用者顯示已讀、只能用 Reaction 模擬;使用者要求「get message detail 後把訊息顯示已讀」,AI 仍說 REST API 做不到

使用者貼截圖問「為什麼 BOT 自己回傳訊息後有已讀」,AI 解釋那是 Webex 內建「查看者」頭像、由客戶端自動處理

![AI 稱查看者是收訊方(你)的已讀狀態、非 bot 標記,附角色×能否顯示已讀對照表;使用者追問「那個頭像是 BOT 的」]((https://ithelp.ithome.com.tw/upload/images/20260806/20183104tRiFbLUahB.png)

AI 改口推翻前說,宣稱 GET /messages/{id} 會自動觸發 bot 已讀;使用者打臉「GET /messages/{id} 不會達到這個效果,你認真一點」

③ 逼它查證:AI 承認「lastSeenActivityId 是我編造的」(chat_14)

AI 承認不該亂猜,改給「正確答案 memberships.updateLastSeen()」,對應 PUT /memberships/{id} 帶 lastSeenActivityId(並稱此欄位公開文件未列)

AI 給完整流程(收 webhook→GET messages→GET memberships→PUT 帶 lastSeenActivityId);使用者要求再查 webex bot message read status

AI 查 Cisco 社群,得知 seen 指示器只顯示最新訊息、沒有未讀 REST API,並誠實說「找不到官方文件說明 bot 如何觸發」

使用者下指令「lastSeenActivityId 是哪裡來的,給我文件」,AI 查證後承認「這個欄位名稱是我之前回答時編造的,非常抱歉」

④ 逼它讀原始碼:追出 updateLastSeen → acknowledge → Mercury 三層呼叫鏈

AI 揭露真實機制(來自 Webex JS SDK 原始碼):updateLastSeen() 呼叫內部 API conversation.acknowledge(),附方式×可用性結論表

使用者要 AI 研究 updateLastSeen() 原理,AI 貼出第一層原始碼,解讀 deconstructHydraId 解碼 + 呼叫 internal.conversation.acknowledge()

AI 分析第二層 internal.conversation.acknowledge():位於標註 internal use only 的內部插件,打的是 Webex 內部 Conversation Service

AI 分析第三層 Mercury WebSocket:送 acknowledge 後 Webex 透過 Mercury 推送 memberships:seen 事件,開始畫整體架構圖

AI 完成整體架構流程圖與關鍵結論表(走內部 API、需 Mercury WebSocket、webhook 模式做不到、需 spark:all+spark:kms scope)

⑤ 卡在 webhook 模式:AI 一再下「webhook 做不到」的結論

使用者問「conversation id 在哪」,AI 從原始碼指出 conversation id 就是 message.roomId / webhook 的 data.roomId,並給出 SDK 參數×來源×webhook 欄位對照表

AI 說明 SDK 內部自動 deconstructHydraId 解碼;使用者問「完整 POST 內容是什麼」,AI 依原始碼給出打 Webex 內部 Conversation Service 的 POST(body verb: acknowledge,object/target 帶內部 UUID)

AI 指出「你拿到的是 Hydra ID 不是內部 UUID」需 Base64 decode,且該 endpoint 是 Webex 內部服務(非公開、cluster 動態、可能有 KMS 加密)

AI 說直接模擬內部請求風險高、建議 Node.js 改用 JS SDK;使用者只回「我是 webhook」,AI 給 Webhook 模式 vs WebSocket 模式對照表(已讀頭像在 webhook 無法、WebSocket 可)

AI 給兩個選項(換 WebSocket 模式,或放棄已讀改用 Reaction)並再次下結論 webhook 做不到;使用者強勢介入「放棄你說不行的想法…我只是在誘導你找答案」並貼 GitHub SDK 連結

⑥ 使用者強勢介入:AI 終於抓 npm 原始碼、兜出可用的程式碼

使用者要 AI 讀 SDK 原始碼、研究 updateLastSeen() 實際打出的 request;AI 改抓 npm registry 原始碼、跑了 12 個指令後開始給出 acknowledge() 實際發送的 HTTP 請求

AI 給出 acknowledge() 的完整 Endpoint 與依區域動態查得的 cluster URL,並列出完整 request body(verb: acknowledge、actor 為 bot UUID、object/target 帶內部 UUID)

AI 指出「你手上沒有 target.url 與加密欄位」,要另從 GET /conversations/{id} 取得;使用者下指令「對話已經有答案,你沒兜起來,幫我寫一個 typescript function 包起來」

AI 整合整段對話,列出拼圖與呼叫鏈(updateLastSeen→acknowledge→prepare/prepareConversation→submit)、實際 POST 摘要,並指出唯一待驗證的是 conversation service URL

AI 產出 Webexmarkasread.ts;使用者要求「包成一個 function、優化內容,太長了」

AI 簡化函式(提醒 getBotUUID 每次打 /people/me、建議初始化時快取 bot UUID)再產出檔;使用者要求分析「之前判斷不行的原因、以及為何一直堅持」,AI 開始反省

⑦ AI 的自我反省:確認偏誤,與「判斷該不該挖」的弱點

AI 反省判斷「不行」的根因:把「沒有公開文件」等同「不可能做到」,只看公開 REST 文件、看到 internal 就放棄、對已讀先入為主,並開始反省「為何一直堅持」

AI 續反省「為何一直堅持」:每次質疑都用新理由支撐同一錯誤結論,本質是確認偏誤;使用者要 AI 分析自己扮演的角色,AI 答「你是已經知道答案的人,在測試我能不能自己找到」(給線索不給答案、反問代替糾正、只在關鍵節點介入)

AI 分析使用者是「不迎合地引導 AI 自己找到答案」;使用者說想更好地使用 AI、要 AI 的邏輯點,AI 總結:AI 傾向在已知邊界內找答案、結論一旦形成難自我推翻、給原始碼比給文件有效、需被告知「答案已經存在」——收尾金句「AI 在執行上很強,在判斷該不該繼續挖上很弱,需要人來把關」


上一篇
《 Day05》把 AI 寫進系統前,先摸清它的四道限制:context window、prompt、法規、模型聰明度
系列文
《讓 AI 去扛人類守不住的戰場,而不是取代人:系統整合商 AI 導入實戰(2026)》6
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言