1. 為什麼要量測工作流程
簡單說:沒有分析,你只是「覺得」,而不是「知道」。
在本模組前面的講座裡,我們已經把情境切成步驟,分配 GPT、小工具與 MCP 的角色,討論了 tool‑gating 與步驟間的狀態保存。現在換成分析的視角來看同一套設計:這一切是否照著預期運作?使用者實際上卡在什麼地方?
在一般的 Web 中大家早就習慣漏斗:有多少人來到登陸頁、把商品放入購物車、走到付款。在 ChatGPT App 中也一樣,只是把頁面換成 workflow 的步驟,把「點擊『購買』按鈕」換成「使用者發言 + 工具呼叫 + 小工具互動」的組合。
當你在沒有指標的情況下設計複雜情境,你看不到:
- 人們最常在哪個步驟「流失」;
- 他們在哪裡停下來讀了一分鐘(或只是去倒茶沒回來);
- 哪個步驟根本沒帶來價值、只是在惹人厭;
- 提示詞或 tool gating 的變更如何影響行為。
按步驟做分析的目標很單純:學會提高完成情境的比例、縮短到達結果的時間、並減少錯誤與客服請求。
從現在起,workflow 不僅是架構物件或 UX 任務,它還是一個可被量測、可用數字說話的東西。
2. ChatGPT App 的情境漏斗
在傳統 Web 中,漏斗通常是線性的: Landing → Product → Cart → Checkout。 在 ChatGPT App 中畫面稍微活潑些:使用者可能用一句話就「跳過」步驟,模型有時也會略過步驟,而且小工具與對話文本可能不同步。
不過基本概念一樣:我們有一連串的步驟,在每一步,部分使用者會往前,另一部分不會。
以我們的 GiftGenius 為例:
- collect_recipient — ChatGPT 與小工具蒐集收件者的基本資料(性別、年齡、關係、興趣)。
- collect_budget — 釐清預算與幣別。
- suggest_ideas — MCP/代理挑選點子,並把禮物卡片回傳到小工具。
- review_selection — 使用者按喜歡/隱藏點子並選出 1–2 個最愛。
- checkout — 建立 commerce intent、完成下單。
把它畫成漏斗大概是這樣:
flowchart TD
A[工作流程開始] --> B["1\. 收件人"]
B --> C["2\. 預算"]
C --> D["3\. 禮物點子"]
D --> E["4\. 選擇禮物"]
E --> F["5\. Checkout"]
但要記得:使用者可能會在聊天裡說「我們直接去付款吧」或「先給我看貴的選項」,模型於是決定跳過部分步驟。因此,ChatGPT App 的步驟分析不僅關乎 UI 畫面,也涉及模型行為:實際經過了哪些步驟、順序是什麼、以及由誰發起—使用者、小工具還是 GPT。
3. 步驟層級的基本指標
先從產品分析的經典開始,稍微調整到 ChatGPT App 的情境。
對每個 workflow,我們至少需要四個基本指標。
為了方便,先彙整成表格:
| 指標 | 含義 | 常見問題 |
|---|---|---|
| Start rate | 有多少使用者啟動了此情境 | 我們的 App 有實際展示給人看到嗎? |
| Completion rate | 有多少使用者走到最後 | 這個情境有多大程度能帶到結果? |
| Conversion per step | 從步驟 N 走到步驟 N+1 的使用者比例 | 究竟是哪個步驟「漏水」? |
| Drop-off per step | 在步驟 N 流失的使用者比例 | 人們最常在哪個步驟放棄? |
通常還會加上一些「努力成本」類的指標:
- 步驟的平均耗時(人們在哪裡「卡住」);
- 步驟的互動次數(需要多少訊息/點擊);
- 以錯誤結束或需要重試的步驟比例。
在 LLM 情境中,還會有更專門的東西,例如模型選擇工具的準確度、或某一步中「幻覺式」回覆的比例。不過那是進階主題,我們會在結尾的模組再談。
對 commerce 情境,還會在步驟之上疊加商業指標:
- 從 workflow 起點到付款的轉換率;
- 從特定步驟(例如「點子推薦」)到付款的轉換率;
- 平均客單價;
- 取消/退貨的比例。
重點在於:這些數字不是彼此孤立,而是有因果敘事。drop‑off 高的步驟不一定不好:也許它在過濾不合適的使用者,往後只剩真的覺得情境有用的人。因此,分析不只是「算百分比」,而是要能用資料說故事。
要能算出這些百分比與漏斗,我們需要原始事件:誰、何時、經過了哪個步驟(或沒經過)。下一節我們約定一下事件格式。
4. 分析事件長什麼樣
寫程式前,先約定我們要從小工具與後端送出的「事件」(event)格式。
一般來說,一個分析事件包含:
- 誰:使用者識別碼,或至少是工作階段識別碼;
- 是哪個 workflow,以及其版本;
- 是哪個步驟;
- 發生了什麼(事件類型);
- 是否成功、花了多少時間;
- 少量中介資料(在地語系、裝置等)。
workflow 的簡化事件結構可描述如下:
export type WorkflowEventType =
| "workflow_started"
| "workflow_finished"
| "step_started"
| "step_completed"
| "step_failed";
export interface WorkflowAnalyticsEvent {
eventId: string; // uuid
timestamp: string; // ISO 字串
userId?: string; // 若可去匿名化
conversationId?: string; // ChatGPT 對話 id(若可取得)
workflowId: string; // 我們的內部識別碼
workflowType: "gift_selection";
workflowVersion: string; // 例如 "1.2.0" 或 "1.2.0-A"
stepName?: string; // collect_budget、suggest_ideas 等
eventType: WorkflowEventType;
toolName?: string; // 若與 tool-call 相關
success?: boolean;
errorCode?: string | null;
durationMs?: number;
metadata?: Record<string, unknown>;
}
幾個重點:
首先,workflowVersion 在你打算做 A/B 測試時非常關鍵:沒有它,你無法知道究竟是哪個版本的情境帶來更好的數字。
其次,conversationId 或其他關聯 ID 可以把事件串起來:小工具中的步驟、MCP 的工具呼叫、以及文字對話。在後續模組我們還會談可追蹤性與可觀測性,但一開始就養成思考「端到端識別碼」的習慣非常有用。
第三,不要把所有東西都塞進事件:訊息全文、e‑mail、地址與其他 PII 最好避免或嚴格匿名化——我們會在最後再詳細討論。
5. 在小工具中埋點(Next.js + Apps SDK)
接著是重頭戲:怎麼讓我們的 GiftGenius 小工具在使用者走情境時,自己安靜地回報步驟。
假設在前面的講座中,你已經實作了類似這樣的東西:
// components/GiftWizard.tsx
type StepId = "recipient" | "budget" | "ideas" | "review" | "checkout";
export function GiftWizard() {
const [currentStep, setCurrentStep] = useState<StepId>("recipient");
const [workflowId] = useState(() => crypto.randomUUID());
// ... 此處渲染不同步驟
}
加上一個小小的「分析層」作為 hook。
useWorkflowAnalytics Hook
做一個知道 workflowId、workflowVersion,並能把事件送到我們的 Next.js API 路由 /api/workflow-analytics 的包裝。
// lib/useWorkflowAnalytics.ts
import { useCallback } from "react";
import type { WorkflowAnalyticsEvent, WorkflowEventType } from "./types";
const WORKFLOW_VERSION = "1.0.0";
export function useWorkflowAnalytics(
workflowId: string,
workflowType: WorkflowAnalyticsEvent["workflowType"] = "gift_selection"
) {
const sendEvent = useCallback(
async (payload: Omit<WorkflowAnalyticsEvent, "eventId" | "timestamp" | "workflowType" | "workflowVersion" | "workflowId">) => {
const event: WorkflowAnalyticsEvent = {
eventId: crypto.randomUUID(),
timestamp: new Date().toISOString(),
workflowId,
workflowType,
workflowVersion: WORKFLOW_VERSION,
...payload,
};
// 簡單送到 API;正式環境可加緩衝/防抖
await fetch("/api/workflow-analytics", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(event),
});
},
[workflowId, workflowType]
);
const trackStepEvent = useCallback(
async (stepName: string, eventType: WorkflowEventType, extra?: Partial<WorkflowAnalyticsEvent>) => {
await sendEvent({ stepName, eventType, ...extra });
},
[sendEvent]
);
return { sendEvent, trackStepEvent };
}
關鍵在於這個 hook 不依賴任何特定 UI 步驟。它只知道 stepName 與 eventType。具體的元件會告訴它:「我開始這個步驟了」、「我完成了」,等等。
送出 workflow_started 與 workflow_finished
在 GiftWizard 元件中,可以在掛載與卸載的時刻註冊情境的開始與結束:
// components/GiftWizard.tsx
export function GiftWizard() {
const [currentStep, setCurrentStep] = useState<StepId>("recipient");
const [workflowId] = useState(() => crypto.randomUUID());
const { sendEvent } = useWorkflowAnalytics(workflowId);
useEffect(() => {
void sendEvent({ eventType: "workflow_started" });
return () => {
void sendEvent({ eventType: "workflow_finished" });
};
}, [sendEvent]);
// ...
}
當然,以卸載來判斷「結束」只是粗略近似:使用者可能只是把聊天收起或切到別的對話。但即使是這麼粗的指標,也能大致看出有多少情境「走到某個地方」。
追蹤各步驟事件
現在來讓每個步驟自己回報分析。先加一個簡單的包裝:
interface StepProps {
stepId: StepId;
onNext: () => void;
trackStepEvent: (stepName: string, eventType: WorkflowEventType, extra?: Partial<WorkflowAnalyticsEvent>) => Promise<void>;
}
function StepRecipient({ stepId, onNext, trackStepEvent }: StepProps) {
useEffect(() => {
void trackStepEvent(stepId, "step_started");
}, [stepId, trackStepEvent]);
const handleSubmit = async () => {
// 驗證、儲存到 widgetState
await trackStepEvent(stepId, "step_completed");
onNext();
};
return (
<div>
{/* 收件者的表單欄位 */}
<button onClick={handleSubmit}>下一步</button>
</div>
);
}
在 GiftWizard 中傳入 trackStepEvent:
export function GiftWizard() {
// ...
const { trackStepEvent } = useWorkflowAnalytics(workflowId);
const goToNext = () => {
setCurrentStep((prev) => NEXT_STEP[prev]);
};
if (currentStep === "recipient") {
return (
<StepRecipient
stepId="recipient"
onNext={goToNext}
trackStepEvent={trackStepEvent}
/>
);
}
// 其餘步驟…
}
同樣地,在可能出錯的步驟(例如 suggest_ideas 中的外部 API 請求)裡,可以在失敗時送出帶有 errorCode 的 "step_failed",在成功載入選項時送出 "step_completed"。
這樣我們就能得到:
- 清楚的事件清單:步驟何時開始、何時結束;
- 計算步驟耗時的能力:"step_started" 與 "step_completed" 的時間差;
- 可見哪些步驟最常以 "step_failed" 結束。
6. 在後端/MCP 埋點
前端的分析很好,但小工具活在相對脆弱的世界:使用者的瀏覽器、iframe、沙盒限制與種種相關問題。因此,建議同時在伺服端—MCP 工具或你的 App 後端 API—記錄事件。
例如你有個 suggest_gifts 工具在做真正辛苦的工作:查 product feed、套用篩選、回傳禮物。在這樣的工具內,你可以同時記錄商業邏輯與分析事件。
以 TypeScript 撰寫的 MCP 工具處理器可能長這樣:
// mcp/tools/suggestGifts.ts
import type { SuggestGiftsArgs } from "../schemas";
import { logWorkflowEvent } from "../analytics/log";
export async function handleSuggestGifts(args: SuggestGiftsArgs, context: { workflowId: string; stepName: string }) {
const startedAt = Date.now();
try {
// 主要的點子篩選邏輯
await logWorkflowEvent({
workflowId: context.workflowId,
workflowType: "gift_selection",
workflowVersion: "1.0.0",
stepName: context.stepName,
eventType: "step_completed",
toolName: "suggest_gifts",
success: true,
durationMs: Date.now() - startedAt,
});
return {
content: [{ type: "text", text: "找到 5 個禮物點子。" }],
_meta: {
// 給小工具的原始資料
},
};
} catch (e) {
await logWorkflowEvent({
workflowId: context.workflowId,
workflowType: "gift_selection",
workflowVersion: "1.0.0",
stepName: context.stepName,
eventType: "step_failed",
toolName: "suggest_gifts",
success: false,
errorCode: "SUGGEST_FAILED",
durationMs: Date.now() - startedAt,
});
throw e;
}
}
而 logWorkflowEvent 可以寫進與前端事件相同的資料表/儲存,只是加個 "source" 標記:"backend"。
為什麼伺服端分析更可靠
首先,工具呼叫不是發生就是沒發生——這是明確事實,不是「使用者好像按了按鈕」的啟發式猜測。
其次,在伺服端比較容易聚合資料:你可以計算每個工具被呼叫的次數、平均 durationMs、以及失敗收場的比例。
第三,你能看出 UX 問題(使用者走不到會呼叫工具的步驟)與技術問題(走得到,但工具常常掛掉)的差異。
7. 如何解讀數據:找出瓶頸
假設你已經從小工具與 MCP 送出了 "workflow_started"、"step_started"、"step_completed"、"step_failed",而且儲存中累積了足夠的資料。再假設你蒐集了一些 GiftGenius 的數據,並拿到步驟層級的摘要。下表是 1000 個已啟動的 workflow 的假想數字:
| 步驟 | 步驟開始次數 | 步驟完成次數 | 該步驟的 drop‑off | 平均時間(秒) |
|---|---|---|---|---|
| recipient | 1000 | 950 | 5% | 12 |
| budget | 950 | 700 | 26% | 35 |
| ideas | 700 | 680 | 3% | 8 |
| review | 680 | 500 | 26% | 40 |
| checkout | 500 | 420 | 16% | 20 |
重點該看哪些:
首先,budget 是明顯瓶頸。drop‑off 高(26%)且平均耗時明顯更久。可能你問太多與幣別/稅務相關的東西、文案不清楚,或是人們對預算沒有把握。這是很好的優化候選:簡化步驟、拆成兩個子步驟、或調整提問方式。
其次,review 也有明顯流失。也許禮物卡片的 UI 過於複雜,或使用者不清楚「按喜歡」的意義。也可能模型回傳太多選項,讓小工具像無止境的清單。這時除了看數字,也該看看截圖/錄影(如果你有),或至少親自以使用者身分走一遍情境。
第三,checkout 流失 16%——對 commerce 情境來說是很可觀的金額。但你要先搞清楚流失在哪裡:下單流程、金流供應商錯誤,還是使用者臨時改變心意。這已經不只是純 UX 的問題,而是 UX + 商務限制的綜合題。
要學會區分 UI 與模型的問題。
- 如果使用者常回到前一步、修改答案——這是提問不清楚或敘述不佳的訊號。
- 如果步驟很快就結束,但其中的工具呼叫常常失敗——這是後端/MCP 的問題。
- 如果步驟很久、且沒有錯誤也沒有回退,可能使用者只是在讀一大段其實不太需要的文字。
8. 實驗與工作流程 A/B 測試
數字本身不會帶來改善。要讓分析有用,你需要會做實驗:調整步驟並比較是否變好。
在 ChatGPT App 的脈絡裡,典型實驗是比較兩個步驟版本或步驟序列:
- 多個簡單畫面的長向導(wizard) vs. 一個複雜表單;
- 不同的提問文案;
- 不同的步驟順序(例如預算是先問還是後問);
- 不同的 tool gating 策略(第一步少一點工具,第二步多一點)。
一個好習慣是把情境版本固定在 workflowVersion 中,並加入實驗識別,例如 "1.3.0-A" 與 "1.3.0-B"。
最簡單的小工具端 A/B 分流
實戰中你會想在使用者或工作階段層級做穩定分配(透過後端),但教學示例用隨機挑選就夠了。
// lib/useWorkflowVariant.ts
import { useMemo } from "react";
export type WorkflowVariant = "A" | "B";
export function useWorkflowVariant(): WorkflowVariant {
return useMemo(() => {
return Math.random() < 0.5 ? "A" : "B";
}, []);
}
在 GiftWizard 中決定變體,並傳給分析:
export function GiftWizard() {
const [workflowId] = useState(() => crypto.randomUUID());
const variant = useWorkflowVariant();
const { sendEvent, trackStepEvent } = useWorkflowAnalytics(
workflowId,
"gift_selection"
);
useEffect(() => {
void sendEvent({
eventType: "workflow_started",
metadata: { variant },
});
}, [sendEvent, variant]);
// 之後可依 variant 調整文字/步驟結構
}
在伺服端,你可以把硬編的 "1.0.0" 換成 "1.1.0-A" 與 "1.1.0-B",或單純記錄 metadata.variant,再在分析端依此分組。
A/B 測試的核心是:預先選好目標指標。例如:「我們要把情境的 completion rate 從 42% 提升到 50%」,或「把 budget 步驟的時間降 20%」。沒有目標指標,任何結構調整都像是在「把櫃子搬了個位置,看起來好像比較美」。
9. 隱私與資料倫理
先前我們提到,在 metadata 與分析事件中,最好不要直球塞入 PII。討論指標時很容易一頭熱,想把一切都記下來。但要記得你運作在 ChatGPT 之中,使用者很可能合理地期待他們的私人訊息不會以原始形式送到外部分析。
幾條簡單守則,現在就該遵守(在安全與 Store 的模組我們會更詳談):
- 首先,不要記錄使用者訊息全文。可以改存訊息長度、答案型態(數字、「是/否」、清單擇一),或匿名化後的特徵,例如「答案為空/不完整/已修改」。
- 其次,不要記錄明確可識別的資訊(PII),除非業務邏輯需要:e‑mail、電話、地址、全名。如果非存不可,把它們放在另一個受保護的區域,並嚴格限制存取。
- 再者,謹慎對待對話脈絡。如果你保存了 conversationId,請確保你不會在沒有正當理由與法源的情況下,嘗試把多個對話「拼接」成超級個人檔案。
- 最後,留意 OpenAI 與 Store 的政策要求(我們會在發布與安全的模組詳細說明),其中明確規範了哪些資料可以帶出 ChatGPT、哪些不行。在設計分析時,最好一開始就納入匿名化與資料最小化,以免之後重寫半個系統。
最後請記住,UX 分析不是全面監控,更不是 Big Brother 式的 surveillance。目標是改善情境、降低使用者挫折,而不是打造一個「誰在凌晨 2:37 沒走到 checkout」的 Big Brother 儀表板。
10. 工作流程 UX 分析的常見錯誤
錯誤 №1:「我們還沒上線,指標之後再說」。
開發者常在 App 已被真實使用者使用時才開始想分析。結果事件是「事後補」、資料支離破碎,幾乎無法比較新舊版本情境。最基本的漏斗("workflow_started"、"step_started"、"step_completed"、"workflow_finished")最好一開始就納入,趁程式碼還相對簡單。
錯誤 №2:只記錄成功、忽略錯誤。
有時 Log 裡只有 "step_completed",而沒有 "step_failed",因為「它不該失敗」。結果你只看到某步驟到達的人很少,但不知道是他們自己離開,還是被錯誤踢出去。永遠同時記錄成功與失敗,至少加上一個粗略的 errorCode。
錯誤 №3:完全沒有綁定 workflow 版本。
你在改文案、調整步驟順序、引入 tool gating,但事件裡的 workflowVersion 一直是 "1.0.0"。一個月後你看圖表,分不清哪些是改動前、哪些是改動後。固定情境版本、必要時加上 A/B 變體,是分析的必備元素。
錯誤 №4:沒有理由就做過度細節的分析。
另一個極端是一開始就設計「完美」的 50 欄事件表,把每個像素的點擊、每次重打的字元都記下來。首先,這可能侵犯隱私。其次,這種資料很難分析,你會溺斃在雜訊中。從能回答具體產品問題的一小組事件與指標開始,必要時再逐步擴充。
錯誤 №5:步驟與情境命名不一致。
有時候程式碼裡叫 budget,分析裡叫 collect_budget,報表裡又變成「問錢的步驟」。幾週後誰也不記得誰是誰。在設計 workflow 時,先約定穩定的步驟識別(stepName),並在 UI、Log 與報表中一致使用。
錯誤 №6:有指標,卻沒人用它們。
最令人沮喪的故事:你辛苦蒐集大量資料,從小工具與 MCP 設好事件送出,結果沒人開儀表板、沒人做決策。為了分析而分析沒有意義;總是問自己:「我能根據這個指標做什麼決策?」如果沒有答案,這個指標暫時就不需要。
GO TO FULL VERSION