step 17 / 19Part VI 可信、產品、上線
S17 產品與 AI UX
在 S6 LLM API 與應用基本功學過串流與取消後,這章把聊天介面做成使用者敢用、能糾正的產品,並回答 S16 安全與治理留下的問題:使用者會核准 93% 的權限提示(approval fatigue),確認畫面怎麼設計才有用?排在 S18 部署與架構之前,因為介面決定了能收集什麼資料、上線後要監控什麼。
- 用幾個具體問題判斷一個需求該不該用 AI,並提出從最小範圍開始的上線方案。(講義與自我檢查)
- 畫出一次 agent 執行的畫面流程(串流、工具狀態、停止、確認、引用),並說明按下停止時,執行中的工具為什麼先標「取消中」。(講義與自我檢查)
- 為 agent 做一套 UI 狀態機,任何時候按停止都不留下不一致的狀態。(必做)
- 把工具呼叫呈現成合適的元件。(必做:工具卡片;進階:generative UI)
- 設計能連回 trace、能流入 eval 資料集的回饋。(必做:explicit;進階:implicit 與流入 eval)
- 主持一次小型易用性測試,並根據結果修正介面。(進階)
Agent 介面要隨時回答四個問題:它在做什麼?它有多少把握?我能不能攔下它?怎麼告訴它錯了?主線是一條流程:串流 → 工具狀態 → 停止 → 確認 → 引用;最後談回饋、主動通知與易用性測試。
★ 一個例子走完全程
Section titled “★ 一個例子走完全程”使用者在筆記助理輸入「把這週的會議筆記整理成摘要,寄給團隊」。下表是一次順利的執行,每一列是畫面的一個狀態(秒數是示意):
| 時間 | 狀態 | 使用者看到的 | 使用者能做的 |
|---|---|---|---|
| 0 s | idle → 送出 |
訊息出現在對話中,送出鈕變成停止鈕 | 停止 |
| 0.3 s | 等待第一個事件 | 助理的位置出現 正在思考… 與跳動的三個點 |
停止 |
| 1.0 s | running_tool |
工具卡片「正在搜尋筆記:會議,本週」,旁邊有轉圈圖示 | 停止、點開卡片看參數 |
| 2.5 s | running_tool |
卡片變成「找到 5 則筆記」並列出標題,下方接著顯示「整理 5 則筆記」 | 停止、點標題開啟原筆記 |
| 5.3 s | streaming |
摘要一句句出現,句尾有引用標號 [1]、[2] | 停止、點引用 |
| 17.3 s | awaiting_approval |
寄信確認卡(agent 只用 email_draft 建草稿):4 位收件人、主旨、完整內文,按鈕「寄出」「修改」「取消」 |
核准、修改、拒絕 |
| 按下寄出後 | done |
確認卡變成「已寄出給 4 人,10:42」;訊息下方出現讚 / 倒讚 | 給回饋、複製、重新生成 |
寄給別人採用 S16 安全與治理 的「草稿 → 使用者按寄出」,agent 不直接呼叫 email_send。
同一個請求也可能走到四條分支:
- 空結果:搜尋找到 0 則。卡片寫「本週沒有會議筆記」,助理不硬寫摘要,而是問「要改查最近 14 天嗎?」
- 錯誤:搜尋工具逾時。卡片標「失敗」並寫原因,助理換方法或請使用者重試;已顯示的內容不消失。
- 中斷:使用者在摘要寫到一半時按停止。文字停在原處並標上「已中斷」,沒有卡片停在「執行中」。
- 拒絕:使用者在確認卡按「取消」。agent 收到「使用者拒絕寄出」這個工具結果,回覆「已取消寄信,摘要保留在上面」,執行不算失敗。
順利路徑一兩天就做得出來;最花時間、最常出 bug 的是這四條分支。
★ 這件事該交給 AI 嗎?
Section titled “★ 這件事該交給 AI 嗎?”設計介面之前先問:多常發生?出錯代價多大?使用者能否快速檢查?有沒有不用 AI 的簡單解法?
| 問題 | 「幫我找筆記」 | 「每週五自動把會議摘要寄給團隊」 |
|---|---|---|
| 頻率 | 每天好幾次 | 每週一次 |
| 出錯代價 | 低,再找一次就好 | 高,信寄出去收不回來 |
| 能否快速檢查 | 能,標題一眼看完 | 難,要逐句對照原筆記 |
| 結論 | 直接做 | 先做「草稿 → 使用者按寄出」 |
第二個功能適合 start small:先做「建議 → 使用者確認」,累積紀錄(例如 20 週裡幾乎不改草稿)再放寬自主程度。
容易誤會:使用者抱怨「不好用」,就換更強的模型。 applied-llms.org 說:模型不是產品,包在外面的系統才是。問題多半出在介面沒讓使用者看懂、攔下或修正 AI。
★ 怎麼讓等待不難熬?
Section titled “★ 怎麼讓等待不難熬?”體感等待取決於多快看到有意義的變化。串流讓文字邊產生邊出現,體感延遲取決於第一個 token 的時間(TTFT,← S6 LLM API 與應用基本功)。人機互動研究常引用 Nielsen(1993,整理自 Miller 1968 與 Card et al. 1991)的三個門檻:0.1 秒內感覺即時;1 秒內思路不會被打斷,但會察覺延遲;超過 10 秒,注意力就會離開,需要看到進度。
用上面的例子算:摘要約 600 個 token,每秒產生 50 個,光寫摘要就 12 秒,加上搜尋與整理共 17.3 秒。不串流也不顯示進度,使用者要盯著空白畫面 17 秒;照上表,畫面沒有任何一段超過 3 秒沒變化。這就是載入狀態(loading state)的原則:第一個 token 前先給佔位提示,工具步驟本身就是最好的進度訊息;跑好幾分鐘的任務改成步驟清單,完成時再通知。
容易誤會:串流讓回答更快完成。 它只讓等待看得見。要先過輸出 guardrail(← S16 安全與治理)的內容、structured output,就顯示進度、完成後一次呈現。
準備動手做:串流顯示的細節
- 不完整的 Markdown:串流到一半的程式碼區塊或表格還沒收尾,直接渲染會閃爍或排版錯亂。可以每次都渲染「目前全文」,並暫時補上未閉合的標記。
- 渲染頻率:每個 chunk 都重新渲染會拖慢頁面,可以每個 animation frame 最多更新一次。
- 自動捲動:使用者往上捲閱讀時,停止自動捲到底部,並顯示「跳到最新」按鈕。
- 邊串流邊檢查:需要輸出檢查時,可以緩衝到一個完整句子再檢查、再顯示,延遲增加一句的時間,但不必等整段。
- 工具參數:工具呼叫的參數串流時是不完整的 JSON(← S6 LLM API 與應用基本功的 partial JSON),卡片上可以先顯示工具名稱,參數完整後再顯示內容。
★ 工具結果怎麼顯示?
Section titled “★ 工具結果怎麼顯示?”Generative UI 指模型的輸出不只是文字,而是決定顯示哪個 UI 元件、填入什麼資料。最常見的是工具呼叫呈現:每個工具有對應的元件,依工具狀態顯示載入、結果或錯誤。元件的輸入在 React 裡叫 props,就像函式的參數。例如 notes_search 回傳 5 則筆記,前端對每一則渲染一張 NoteCard,props 是 { title: "週一產品會議", date: "2026-11-02", snippet: "決定把上線日延到…", noteId: "n_381" },使用者看到的是可點開的卡片,而不是一段 JSON。
卡片也是透明度的來源,要分層:預設只顯示一行「正在搜尋筆記:報稅」,點開才看到完整輸入與輸出。12 個工具呼叫全展開等於沒有資訊。
容易誤會:generative UI 是讓模型寫任意 HTML。 通常是從做好的元件挑一個、填入 props。props 是不可信資料,渲染前要驗證格式;Markdown 要先 sanitize(清掉可執行的內容與自動載入的連結),不自動載入不可信的圖片或連結(EchoLeak 就是靠自動載入的 URL 送出資料,← S16 安全與治理)。
深入一點:AG-UI、MCP Apps 與現成框架
元件之外,有兩個協定解決不同兩方之間的問題:
- AG-UI 解決「agent 後端 ↔ 你的前端」:一套事件協定,定義文字串流、工具呼叫、狀態快照與差異、執行生命週期等事件。不同的 agent 框架只要都送 AG-UI 事件,就能接上同一個前端。分類是:文字訊息的開始 / 內容 / 結束、工具呼叫的開始 / 參數 / 結束、狀態快照與差異、一次執行的開始 / 結束 / 錯誤;CopilotKit 是它的第一方 client。必做題的假後端可以照它定事件。
- MCP Apps 解決「MCP server ↔ 第三方 host」:MCP 的官方擴充(穩定版 spec 為 2026-01-26),讓 MCP server 提供自己的互動 UI,在 AI 助理這類 host 裡顯示(MCP ← S10 工具與 MCP)。運作方式是工具連結一個
ui://資源,host 把它放進 sandbox 的 iframe 渲染,兩邊用 JSON-RPC 溝通。MCP-UI 是它的前身社群專案,現在已併入這個標準,並提供 SDK。
只做自己的 web app、不打算被別人的 host 嵌入時,用不到 MCP Apps;AG-UI 也是選用的,自己定事件格式一樣可以。
Vercel AI SDK 把每個工具呼叫表示成一個帶 state 的 message part,例如 input-available(參數已完整、正在執行)、output-available(有結果)、output-error(失敗)。前端依 state 切換卡片外觀。它沒有「取消中」或「已取消」狀態,要自己從 abort 與後端查到的結果推出。
★ 停止、錯誤、空結果怎麼處理?
Section titled “★ 停止、錯誤、空結果怎麼處理?”把一次執行當成明確的狀態機,前面的表格就是它的規格。執行的狀態是 idle → streaming → running_tool → awaiting_approval → done,另外還有 interrupted 與 error。每個工具呼叫也有自己的狀態:
| 工具狀態 | 什麼時候進入 | 卡片顯示 |
|---|---|---|
awaiting_approval |
要求核准,工具尚未執行 | 確認卡 |
running |
工具開始執行 | 執行中 |
cancelling |
執行中被要求取消或斷線,結果未知 | 「已要求取消,正在確認…」 |
completed |
收到結果,或查證確認已完成 | 完成 |
cancelled |
查證確認沒有執行,或停止時還在等核准 | 已取消 |
failed |
執行失敗,或查證確認失敗 | 失敗與原因 |
停止要一路傳到後端(取消模型串流、要求中止工具);部分完成的訊息標成「已中斷」,下一輪 context 也要知道。難的是已發出的副作用:使用者按下「寄出」後、寄信還在執行時按停止,信可能已經寄出。所以卡片不能直接標「已取消」,要先進 cancelling,畫面寫「已要求取消,正在確認信件是否已寄出」。後端要先確認工具已停止(中止成功或已執行完),再用 S12 Agent loop 與 harness 的 operation ID(當成 idempotency key;還沒讀 S12 的話,就是 S6 LLM API 與應用基本功學過的 idempotency key)查實際結果,這時查到的才算數;工具還在跑時查不到 key,不代表沒寄出,要繼續留在 cancelling。查到後才標成 completed(「已寄出給 4 人」)、cancelled(「未寄出」)或 failed,並寫進下一輪的 context。停止時還在等核准的工具從未執行,直接標 cancelled,不必查證。
錯誤分兩種:工具失敗是正常事件,卡片標失敗、結果回給 agent 換方法;連線中斷要保留已收到的內容,顯示「重試」。空結果不是錯誤:明確告訴 agent 和使用者「沒找到」並給下一步,否則模型容易憑空補內容。
使用者自己的動作(例如刪除一則記憶)可以樂觀更新(optimistic update):先改畫面,失敗再回滾。agent 的動作不行:畫面先顯示「已寄出」再改口,比慢一秒更傷信任。
容易誤會:停止就是把轉圈拿掉。 前端停了,後端可能還在跑(← S6 LLM API 與應用基本功);直接標「已取消」,則可能謊報一封其實已寄出的信。
準備動手做:狀態機與取消的實作要點
- 用事件驅動:狀態機只接收事件(文字片段、工具開始、工具結果、要求核准、結束、錯誤、使用者取消),輸出目前狀態。UI 只讀狀態,不自己判斷。這樣不寫前端也能用測試驅動它跑完所有劇本。
- 工具呼叫用 ID 索引:每個工具呼叫存成
{ id, name, args, status },事件依 ID 更新對應的那一筆。 - 終止不變量:進入
interrupted或error時,所有running的工具改成cancelling;停止時尚未核准的工具直接標cancelled(從未執行,不必查證);cancelling只有在查到實際結果後才轉成completed、cancelled或failed。查不到(例如後端也斷線)就留在cancelling,卡片改寫「結果未知,請自行確認」,不要猜。測試斷言兩件事:終止後沒有任何工具是running;假後端回覆查詢後,沒有任何工具停在cancelling。 - 取消的路徑:前端
AbortController.abort()→ 後端偵測到 client 斷線(或收到取消請求)→ 停止 generator、取消模型串流、要求中止工具 → 記 log(例如「cancelled at event N」,方便量測延遲)。假後端收到取消後停止送事件;工具的實際結果用另一個請求依工具呼叫 ID 或 S12 Agent loop 與 harness 的 operation ID(當成 idempotency key)查詢;後端要先確認工具已停止,查到的結果才算數,工具還在跑時查不到 key 不代表沒執行。Python 的串流框架要確認 client 斷線時 generator 真的會停。 - 核准是一種工具結果:拒絕時回給 agent 的是「使用者拒絕」這個結果,執行繼續,而不是拋出例外結束整個執行。
★ AI 出錯時怎麼好撤回?
Section titled “★ AI 出錯時怎麼好撤回?”Defensive UX 的前提是 AI 一定會出錯,要讓錯誤容易發現、容易丟掉。Eugene Yan 整理的原則:
- 期待管理:先說清楚能做什麼、不能做什麼,例如「我不會未經你確認就寄信」。
- Easy dismissal:用最小成本拒絕建議,例如補全用淺色文字,繼續打字就消失。
- Attribution:標出哪些是 AI 產生、根據哪些來源,例如「由助理建立」加來源連結。
容易誤會:有「關閉」按鈕就算 easy dismissal。 拒絕要點三下、接受只要一下,使用者就會傾向接受,等於把 AI 的錯誤推給他。
★ 使用者該信它幾分?
Section titled “★ 使用者該信它幾分?”讓使用者的信任和 AI 實際的可靠度相符,叫信任校準(trust calibration):太高錯誤會流出去,太低使用者每次重查。S8 Eval 方法論學過 calibration 與 abstention,這裡處理怎麼呈現:
- 不顯示看不懂的原始分數,用少數幾個等級,例如「有來源佐證 / 部分佐證 / 找不到來源」。
- 對 RAG 回答,最有用的訊號通常是引用(← S9 檢索與 RAG):每句標出來源,點開跳到原筆記的對應段落。
- 選擇不回答時說明原因並給下一步,例如「筆記裡沒提到報稅截止日,要列出相關的 3 則嗎?」
找不到來源的句子有三種呈現:用不同樣式逐句標示(資訊最完整)、直接刪掉(最保守,但可能刪掉正確的常識)、開頭註明「部分內容沒有出處」(最省版面,但不知道是哪一句)。出錯代價高的領域適合逐句標示,讓使用者知道哪一句要自己查。
等級要先驗證和正確率有關。例如 200 題 golden set 中,標「有來源佐證」的 150 題答對 141 題(94%),標「找不到來源」的 50 題答對 30 題(60%);兩組差不多的話,顯示它只會誤導。
容易誤會:顯示「信心 92%」就是做到校準。 使用者很難解讀這個數字,模型自己報的信心也常常不準(← S8 Eval 方法論)。
深入一點:逐筆的不確定性訊號從哪來
S8 Eval 方法論的 judge 是離線評估用的,線上每一筆回答需要自己的訊號,而 Claude 的 API 不回傳 logprobs。常見做法:
- Self-consistency:同一題多跑 2–3 次,比對答案是否一致;不一致就降級。成本是 2–3 倍的呼叫。
- 線上 judge:每筆回答跑一次在 S8 Eval 方法論校準過的 judge,延遲與成本都會增加。
- 引用覆蓋率:有來源佐證的句子佔多少比例(← S9 檢索與 RAG 的 citation 指標),幾乎不增加成本,最適合 RAG 類的回答。
選好訊號後,照上面 150 / 50 的方式在 golden set 上分組算正確率,確認等級越高、正確率越高,再拿來顯示。這就是進階題 A17 完整前端(代號說明)的第 0 步。
★ 什麼時候該問使用者?
Section titled “★ 什麼時候該問使用者?”確認畫面只有在少見、內容看得懂時才有用;這就是 S16 安全與治理留下那個問題的答案。人工介入(human-in-the-loop,HITL)的三個原則:
- 只對高風險動作確認。依 S10 工具與 MCP 的風險分級:唯讀不問;可復原的不問、事後提供「復原」;不可復原或影響他人的才問(auto 模式的做法;ask 模式仍會問中風險,← S12 Agent loop 與 harness)。
- 顯示實際會發生什麼。完整的收件人與內文、檔案的 diff(改了哪些行的對照),而不是只問「確定嗎?」。
- 拒絕要便宜。「修改」與「取消」和「核准」一樣顯眼,拒絕後 agent 繼續工作。
算一次:一次執行 12 個工具呼叫,8 個唯讀、3 個可復原(建立或編輯筆記)、1 個寄信。每個都問是 12 次確認;照分級只剩 1 次,使用者一看到就知道「這次要注意」。也可以讓 S16 安全與治理的 action classifier 先判斷哪些要問人。
容易誤會:確認越多越安全。 多到使用者反射性按核准,效果比少而精的確認更差,還多了「使用者核准過」這個錯誤的安全感。
★ 怎麼收集使用者回饋?
Section titled “★ 怎麼收集使用者回饋?”回饋把每次使用變成改進系統的資料,前提是每一筆都能對回 trace。Explicit 回饋是使用者主動給的(讚 / 倒讚、錯誤原因):訊號清楚,但數量少、偏向不滿意的人。Implicit 回饋從行為推斷(複製、編輯草稿、重新生成):數量多,但需要解讀。
例子:一週 1,000 則回答,只有 30 則被按讚或倒讚(3%),卻有 240 則被複製、90 則被重新生成。一份 200 字的草稿被改掉 120 字,修改比例 60%,可以當成一筆「草稿品質差」的訊號。
容易誤會:implicit 訊號可以直接當好壞。 複製可能是覺得好,也可能是要貼給同事抱怨;重新生成可能是答錯,也可能想看另一種寫法。先抽一批 trace 對照,才知道訊號代表什麼。
兩種回饋都要帶 trace ID。再給自己做一個 reviewer UI:看 trace、標通過 / 失敗、一鍵加入 golden set;它是 S14 Agent eval、資料與最佳化 那套 data flywheel 的入口。
準備動手做:回饋事件的格式
- 最少欄位:
trace_id、message_id、type(例如thumb、copy、regenerate、edit)、value(讚 / 倒讚、修改比例等)、可選的reason、時間戳記。少了前兩個,這筆回饋就接不回 trace。 - ID 從後端來:trace ID 與 message ID 由後端在串流開始時送給前端,前端不要自己產生,否則對不上後端的 trace。
- 修改比例:可以用編輯距離除以原稿長度;只要同一套計算方式前後一致,就能比較改版前後的變化。
- Reviewer UI 只給自己用:列出有負面回饋的 trace、標失敗類型、匯出到 eval 資料集。Gradio 或 Streamlit 就夠了。
★ 主動通知怎麼不煩人?
Section titled “★ 主動通知怎麼不煩人?”主動型 agent(proactive agent)不等使用者開口就發訊息,重點是打擾的成本。做法:設通知預算(例如每天最多 3 則)、能合併就做成每日摘要、每則都能一鍵關閉同類通知,並追蹤「通知後有沒有採取行動」。點擊率低時,先按通知類型與發送時間拆開,看哪一類沒人點,再決定合併、改時間或停掉。S19 真實 agent 系統與畢業專題會實作主動型 agent。
個人化介面(← S13 Memory 與個人化)要讓使用者看得到、能編輯 agent 記得什麼(「因為你說過偏好條列式摘要」)。
容易誤會:個人化越不著痕跡越好。 看不見的個人化容易讓人覺得被監視;看得見又能修正的,才會累積信任。
★ 怎麼知道介面好不好用?
Section titled “★ 怎麼知道介面好不好用?”易用性測試看真實使用者操作介面,量的是行為,不是意見。找幾位沒參與開發的人做具體任務,邊做邊說出想法(think-aloud);你只觀察,不提示。每個任務記:完成與否、花多久、卡在哪裡;修正最嚴重的問題後重測。
例子:任務「把上週的報稅筆記整理後寄給會計師」。第二位使用者沒完成:確認卡要往下捲才看得到,他以為助理當掉了。把確認卡固定在輸入框上方後重測,1.5 分鐘完成。
容易誤會:問「你覺得好用嗎?」就夠了。 得到的多半是客套話。2 位使用者足以找出最嚴重的問題,但要比較改版成功率需要更多人(← S8 Eval 方法論)。
○ 語音介面有什麼不同?
Section titled “○ 語音介面有什麼不同?”語音介面的延遲要求更嚴格(說完到開始回應通常要在一秒內),還要處理插話(barge-in)與輪流說話。架構上可以用語音到語音的 realtime 模型,或串接語音辨識、LLM、語音合成三段。
○ 瀏覽器端模型的產品取捨?
Section titled “○ 瀏覽器端模型的產品取捨?”S6 LLM API 與應用基本功的選修介紹過在瀏覽器裡跑模型(transformers.js、WebLLM、Chrome built-in AI)。產品上的取捨是:資料不離開裝置、可以離線,換來第一次要下載模型的等待,以及只能用小模型。適合隱私敏感或離線的小任務,介面要處理「模型下載中」這個額外的載入狀態。
○ AI 會怎麼改變工作?
Section titled “○ AI 會怎麼改變工作?”AI 產品會改變工作的分工方式。有研究嘗試用真實職業任務評估模型能做到什麼程度(例如 GDPval)。這個主題不影響實作,但會影響你判斷產品該取代哪些工作、又該輔助哪些工作。
理解路線(不寫程式):做完「先試一下」就可以跳到自我檢查。
先試一下(約 10 分鐘)
Section titled “先試一下(約 10 分鐘)”-
回答約 400 個 token,每秒產生 40 個,之前先跑 2 秒搜尋。不串流的話等多久才看到東西?前 2 秒畫面該出現什麼?
對照答案
400 ÷ 40 = 10 秒,加上搜尋共 12 秒才看到東西。照講義做法:0.1–1 秒內先出現
正在思考…,接著是「正在搜尋筆記」的工具卡片,2 秒後文字開始串流,畫面一直有變化。 -
寄信工具執行到一半時使用者按停止。卡片下一個狀態是什麼、寫什麼?前端、後端、下一輪 context 各該發生什麼?
對照答案
卡片進
cancelling,不能直接寫「已取消」。後端停止產生事件、要求中止工具,確認工具已停止後,再用 operation ID(當成 idempotency key)查信寄了沒;查到後才改成「已寄出」「未寄出」或「失敗」,並寫進下一輪的 context。
必做:Agent 介面的四個狀態問題
Section titled “必做:Agent 介面的四個狀態問題”目標:做一個不依賴真實模型的 agent 前端,正確處理串流、工具呼叫、核准、中斷四種狀態。
步驟
- 寫一個假的 agent 後端:照預先寫好的劇本,用 SSE 送出事件,包括文字片段、工具呼叫開始、工具呼叫結果、要求核准、執行結束、錯誤。事件格式可以自己定,也可以照 AG-UI 的事件類型。假後端另外提供一個端點,依工具呼叫 ID / idempotency key 查詢工具的實際結果。如果你在 S6 LLM API 與應用基本功錄過 record / replay fixtures,可以直接拿來用。
- 準備 5 個劇本:(a) 正常回答,中間有 2 次工具呼叫;(b) 工具失敗後 agent 改用別的方法;(c) 使用者在寄信工具執行中按下停止,分兩種結果:停止時已寄出、未寄出;(d) 高風險工具要求核准,使用者拒絕;(e) 串流途中斷線。
- 前端用明確的狀態機管理一次執行,每個工具有自己的卡片元件,依狀態顯示「執行中 / 取消中 / 完成 / 失敗 / 已取消」;「取消中」要等查到工具的實際結果才轉成其他狀態;停止時還在等核准的工具直接標「已取消」。
- 停止按鈕要一路取消到後端:假後端收到取消後必須停止送事件,並在 log 記下「cancelled at event N」。
- 每則 assistant 訊息有讚 / 倒讚與一個可選的原因欄位;送出的回饋事件要帶 trace ID 與 message ID,寫進本地的
feedback.jsonl。
不寫前端的版本:步驟 1、2、4、5 不變,步驟 3 改成三件事:
- 狀態機寫成一個不依賴 UI 的類別:事件進、狀態出,每個工具呼叫有自己的狀態(執行中 / 取消中 / 完成 / 失敗 / 已取消)。
- 劇本測試:用 pytest(或 Vitest)寫一個 client,直接連假後端的 SSE、驅動狀態機跑完 5 個劇本,斷言每個劇本的最終狀態、每個工具呼叫的狀態,以及沒有任何工具停在「執行中」。劇本 (c) 由測試在指定事件後送出取消;劇本 (d) 由測試送出「拒絕」。
- 最小介面:用 Gradio 或 Chainlit 接上同一個狀態機,至少手動跑過劇本 (a)、(c)、(d)、(e)。Gradio 的
gr.ChatInterface串流時會把送出鈕換成停止鈕,工具呼叫可以用gr.ChatMessage的metadata(title與status:pending/done)顯示;核准與拒絕用兩個按鈕。注意 Gradio 的停止只會停掉你的 generator,要確認 generator 結束時有關掉連往後端的串流,假後端才收得到取消。
驗收
- 5 個劇本全部正確渲染(5/5),每個劇本附一張最終狀態的截圖((c) 已寄出、未寄出各一張)。
- 劇本 (c):從按下停止到後端 log 出現取消紀錄 ≤ 500 ms(用瀏覽器 devtools 或後端時間戳記量);停止後沒有任何卡片仍顯示「執行中」;假後端回覆最終結果後沒有工具停在「取消中」,且卡片顯示的結果與假後端一致(已寄出、未寄出兩種都要對)。
- 劇本 (d):核准畫面顯示工具名稱與完整參數;拒絕後 agent 收到「使用者拒絕」的結果,而不是整個執行失敗。
- 劇本 (e):畫面顯示中斷訊息並提供重試,已收到的部分內容沒有遺失。
feedback.jsonl中 100% 的回饋都有 trace ID 與 message ID。- 不寫前端的版本:
- 「正確渲染」改成 5 個劇本測試全部通過(5/5)。
- 截圖改成每個劇本的狀態轉移 log,手動跑過的劇本另附介面截圖。
- (c) 的 500 ms 用測試送出取消的時間戳記和後端 log 相減;測試也要斷言假後端回覆最終結果後沒有工具停在「取消中」,且工具狀態與假後端的結果一致。
- (d) 的核准內容與 (e) 的中斷訊息要出現在最小介面上,(e) 已收到的部分內容由測試斷言沒有遺失。
提示
- TypeScript:React + Vercel AI SDK 的
useChat(tool part 的 state 大致對應卡片狀態;「取消中」與「已取消」要自己從 abort 與查到的實際結果推出),或直接用fetch+ReadableStream+AbortController自己寫;狀態機可以用 XState 或一個 reducer。 - Python:後端用 FastAPI 的
StreamingResponse或 sse-starlette,注意在 client 斷線時停止 generator;介面與測試見上面「不寫前端的版本」。 - 卡住時:
- 重讀:一個例子走完全程、停止、錯誤、空結果怎麼處理?(含「準備動手做」)
- 延伸:Vercel AI SDK〈Generative User Interfaces〉、AG-UI 事件類型
提醒:選「不寫前端的版本」的話,劇本測試與最小介面都要做。
進階:系統專案 A17:你的系統的完整前端與易用性測試
Section titled “進階:系統專案 A17:你的系統的完整前端與易用性測試”目標:把你的系統做成兩位真實使用者能完成任務的介面,並讓介面產生的回饋流進 eval 資料集(← S14 Agent eval、資料與最佳化)。
步驟
- 先備:逐筆回答的不確定性訊號。A8 錯誤分析與 golden set 的 judge 是離線評估用的,前面的系統專案沒有替每一筆線上回答產生不確定性訊號;Claude 的 API 也不回傳 logprobs。如果你還沒有,先加一個:例如 self-consistency(同一題多跑 2–3 次,比對答案是否一致)、線上對每筆回答跑同一個 judge,或用引用覆蓋率(有來源佐證的句子比例,← S9 檢索與 RAG)。在 golden set 上確認這個訊號的等級和正確率有關,再拿來顯示。
- 把必做題的狀態機接到你真實的 agent(← S12 Agent loop 與 harness),每個工具都有對應的呈現元件,至少 2 個工具使用 generative UI(結構化的卡片,而不是 JSON 字串)。
- 引用(← S9 檢索與 RAG):回答中的每個引用都能點開,跳到原始來源的對應段落;找不到來源的句子用不同樣式標示。
- 不確定性(← S8 Eval 方法論):依步驟 0 的訊號,顯示三個以下的等級;系統選擇不回答(abstention)時說明原因。
- 確認:依 S10 工具與 MCP 的分級,或經 S16 安全與治理稽核後標為高風險的動作,一律顯示實際內容(收件人、diff 等)再讓使用者確認。
- 回饋:explicit(讚 / 倒讚 + 原因)與至少 2 種 implicit 訊號(例如複製、編輯 AI 草稿、重新生成),全部帶 trace ID 寫進 trace store。
- Reviewer UI:一個只給你自己用的頁面,列出有負面回饋的 trace,可以標記失敗類型,並一鍵匯出到 eval 資料集(← S14 Agent eval、資料與最佳化)。
- 記憶檢視(← S13 Memory 與個人化):使用者可以看到、編輯、刪除系統記住的內容。
- 易用性測試:找 2 位使用者,各給 3 個任務,請他們邊做邊說出想法(think-aloud)。記錄每個任務是否完成、花多久、卡在哪裡。修正最嚴重的 2 個問題後,請至少 1 位使用者重測。
驗收
- 2 份易用性測試紀錄,每份 ≥ 3 個任務,記錄完成與否、時間、問題;修正前後至少 1 個任務的完成時間或成功率有比較。
- 測試期間產生的回饋事件 100% 能對應到 trace。
- 從 reviewer UI 匯出 ≥ 10 個標註過的案例到 eval 資料集(← S14 Agent eval、資料與最佳化),並實際用那套 eval 跑過一次。
- 在任何狀態按下停止,都不會留下「執行中」的元件,也不會讓後端繼續執行工具(用 5 次隨機時間點測試,5/5 通過)。
以筆記助理為例:搜尋筆記的結果顯示成可點開的筆記卡片;「把這週的會議筆記整理成摘要寄給團隊」會先顯示草稿與收件人讓使用者確認;使用者把 AI 草稿改了一半,這個編輯距離就是一筆 implicit 回饋。
提示
- TypeScript:Vercel AI SDK 或 CopilotKit(AG-UI 的第一方 client);引用跳轉可以用 URL fragment 或 Text Fragments。
- Python:這一題需要一個真正的前端。如果前端不是你的主力,先用 Vite 的 React 模板加 Vercel AI SDK 的
useChat(S6 LLM API 與應用基本功延伸閱讀中的 Chatbot 範例幾乎能直接用);FastAPI 後端只要輸出 AI SDK 的 stream protocol。沒寫過前端的話,要先補一些前端基本功。Reviewer UI 只給你自己用,Gradio 或 Streamlit 就夠了。 - 卡住時:
- 重讀:使用者該信它幾分?、怎麼收集使用者回饋?、怎麼知道介面好不好用?
- 延伸:Eugene Yan 的 Collect user feedback 段;Microsoft Guidelines for Human-AI Interaction 當檢查清單
-
用自己的話解釋:為什麼 implicit 回饋比讚 / 倒讚多,卻更難用?舉一個你系統裡的 implicit 訊號,說明它可能被誤讀的情況。(理解路線:用筆記助理回答)
提示
判斷重點:同一個行為有多種解讀(複製可能是滿意,也可能是要轉貼抱怨),要抽 trace 對照才知道意思。常見錯法:把「被複製」直接算成好評。回讀:怎麼收集使用者回饋?
-
Generative UI 為什麼通常是挑做好的元件、填 props,而不是讓模型寫 HTML?props 渲染前要做什麼?
提示
判斷重點:事先做好的元件外觀與行為可控;props 來自模型或工具輸出,是不可信資料,渲染前要驗證格式,Markdown 要 sanitize、不自動載入外部圖片。常見錯法:以為模型自己產生的內容可以直接渲染。回讀:工具結果怎麼顯示?
-
你的 RAG 回答有一句話找不到來源。列出三種呈現方式,並說明你會選哪一種、為什麼。
提示
判斷重點:高風險領域選逐句標示。常見錯法:直接刪掉,使用者不知道內容被改過。回讀:使用者該信它幾分?
-
一個主動提醒功能上線一週,使用者對通知的點擊率是 5%。你會先改什麼?要看哪些資料再決定?
提示
判斷重點:先按通知類型與發送時間拆開,找出哪一類沒人點,再決定合併、改時間或停掉。常見錯法:直接全部改成每日摘要,沒先看資料。回讀:主動通知怎麼不煩人?
-
不看講義,替筆記助理的「把上週提到報稅的筆記整理成清單」走一遍:列出每個狀態下使用者看到什麼、能做什麼,再寫出搜尋找到 0 則時畫面應該怎麼呈現。
提示
判斷重點:每個狀態都要有畫面與可做的動作(至少能停止);0 則時明確說「沒找到」並給下一步,助理不硬寫清單。常見錯法:漏掉等待中或工具執行中的畫面,或沒給停止。回讀:一個例子走完全程
-
情境題:筆記助理每次執行平均跳出 6 個確認,使用者幾乎全部直接按核准。你會怎麼重新設計?哪些動作還需要確認,確認卡上要顯示什麼?
提示
判斷重點:依風險分級只留不可復原或影響他人的動作,可復原的改成事後「復原」;確認卡顯示實際內容(收件人、內文、diff),拒絕和核准一樣容易。常見錯法:只把確認卡做得更醒目,數量不變。回讀:什麼時候該問使用者?
講義已經涵蓋這一章需要的內容。下面的資料適合想看原始出處、現成框架的做法,或更多產品案例的人,都不是必讀。
原則與設計模式
Section titled “原則與設計模式”- Eugene Yan: Patterns for Building LLM-based Systems & Products(2023)— Defensive UX 與 Collect user feedback 兩段是講義這兩節的原始出處,有更多產品實例。
- applied-llms.org: What We’ve Learned From A Year of Building with LLMs(2024-06)— Strategic 段完整說明「the model isn’t the product」與從小範圍開始的理由。
- Microsoft ai-agents-for-beginners L03: AI Agentic Design Principles — 少數直接談 agent UX 的教材,範例綁 Azure,原則通用。
- Microsoft: Guidelines for Human-AI Interaction(2019)— 完整的 18 條原則,可以當 UI 稽核清單。
- Nielsen Norman Group: Response Times: The 3 Important Limits — 講義 0.1 / 1 / 10 秒門檻的出處,說明每個門檻該給使用者什麼回饋。
官方文件與協定
Section titled “官方文件與協定”- Vercel AI SDK: Generative User Interfaces — 工具呼叫 → 元件的具體程式碼,以及 tool part 的各種 state。
- AG-UI Overview — 事件類型一覽,看完能知道 agent 前端要處理哪些狀態;做必做題的假後端時可以照它定事件。
- modelcontextprotocol/ext-apps(MCP Apps)、MCP-UI — MCP server 提供 UI 的標準與 SDK。
- Chip Huyen《AI Engineering》(付費書)ch1(判斷要不要做 AI 應用)、ch10(架構與使用者回饋);配套 repo 有摘要。
- Stanford CS 224G — 創業導向,產品面講次。
- Stanford CS 329Z Proactive Agents 講次(投影片公開後再讀)。
- 台大 陳縕儂 ADL 2026 — 繁中講課;Human-AI Interaction 講次。
- iThome 2025〈AI 產品與架構設計之旅:從 0 到 1,再到 Day 2〉 — 繁中;產品與架構實戰(內容未逐篇審閱)。
- iThome 2025〈前端工程師的AI應用開發實戰:30天從Prompt到Production〉 — 繁中;前端視角(內容未逐篇審閱)。
講義內容以 CC BY-NC-SA 4.0 授權:可以轉載與改作,但要標示出處、不得用於商業用途,改作也要用相同授權。引用的第三方內容與商標屬於原作者。