CodeGym /課程 /ChatGPT Apps /以多步驟 workflow 降低認知負荷

以多步驟 workflow 降低認知負荷

ChatGPT Apps
等級 11 , 課堂 0
開放

1. 在 ChatGPT App 情境下,什麼是 workflow

既然你已經搞定授權,那就值得一份獎勵。讓我們進入非常有趣的主題——ChatGPT App 裡的 workflow。很多人一聽到「workflow」就會回想起 BPMN 圖與沉悶的企業軟體。別擔心:在 ChatGPT App 的語境中,我們關心的是輕量得多的版本。

在本課程中,我們把 workflow 理解為多步驟情境,其中:

  • 有清晰的目標(例如挑選禮物並完成購買),
  • 有連續的步驟(詢問 → 產生選項 → 釐清 → 完成),
  • 每個步驟中,GPT、widget 與工具各有其角色。

重點:workflow 不是「MCP 伺服器中的一個方法」。它是一種組合:

  • 模型的推理(問什麼問題、何時呼叫哪個工具),
  • tools 的呼叫(MCP/Agents),
  • widget 裡的 UI 步驟,
  • 後端中的狀態。

也就是說,你不會只有一個「萬能工具」solve_everything,而是在不同階段啟用多個簡單工具。你也不會有一個「巨型 widget」,而是少量的畫面/狀態,各自處理其子任務。

workflow 中的「責任三角」

可以把 workflow 想成三個參與者的舞蹈:

角色 在 workflow 中的任務 GiftGenius 中的例子
GPT 大腦。理解使用者意圖,決定何時步驟完成、下一步是什麼。可以呼叫 tools。 理解「我想找給極客的東西」,並決定呼叫 search_items(category="geek")
Widget 門面。渲染目前步驟,只顯示相關內容,收集點擊與輸入。保存 UI 狀態。 先顯示表單「送給誰?」,接著是禮物卡片,最後顯示「購買」按鈕。
MCP/Agent 雙手。執行繁重且結構化的工作,驗證資料,保存商務狀態。 保存收禮者的個人檔案,查詢禮物目錄,依預算過濾。

這三個角色共同實現同一個情境,但位於不同層次:GPT 決定「接下來怎麼做」,widget 顯示「此刻要做什麼」,MCP 則負責資料實際發生了什麼。

2. 以 GiftGenius 為例的 workflow

以熟悉的 GiftGenius 情境——禮物精選助手——為例。它可以描述成一個簡單的線性精靈。

步驟可能如下:

  1. 收集收禮者的基本資訊。
  2. 設定預算與限制。
  3. 產生並過濾禮物點子。
  4. 顯示候選項,允許喜歡/隱藏。
  5. 前往結帳(Checkout)或保存這次挑選。

同樣的情境也可視為一個小型「狀態機」:

stateDiagram-v2
    [*] --> Profiling
    Profiling --> ProfilingDone: 個人檔案已填寫完成
    ProfilingDone --> Browsing: 已產生點子
    Browsing --> Refining: 使用者已調整篩選條件
    Refining --> Browsing: 列表已更新
    Browsing --> Checkout: 已選定禮物
    Checkout --> Success: 訂單已完成
    Success --> [*]

其中:

  • Profiling — 蒐集收禮者資料的步驟,
  • Browsing/Refining — 操作候選列表,
  • Checkout — 結帳,
  • Success — 最終確認。

請注意:圖上沒有任何按鈕,也沒有 fetch。這些是邏輯步驟;具體的 UI 畫面、tools 與 API 呼叫,都會掛在這些步驟之上。

3. 為何要把任務切成步驟

如果你曾做過「單一畫面塞 25 題問卷」的介面,你大概知道為什麼。但讓我們條理化地拆開來看。

使用者的認知負荷

人的注意力資源有限。心理學常提到米勒定律:短期記憶大約能容納 7±2 個項目。對 UX 設計來說,這意味著:同時顯示的欄位與選項越多,使用者越容易卡住、疲乏,或直接關掉分頁。

在 ChatGPT 的內嵌小工具中,一個畫面放 12 個欄位的表單,幾乎保證會導致 rage‑quit:使用者會放棄並關閉分頁。使用者是來「對話」的,不是來考試。

但如果你把任務切成步驟:

  • 「步驟 1/4:請介紹對方」
  • 「步驟 2/4:選擇預算」
  • 「步驟 3/4:瀏覽選項」
  • 「步驟 4/4:確認選擇」

那麼每個時間點看起來都可負擔。進度列或步驟標示會帶來掌控感:知道正在發生什麼、還剩多少。

模型的認知負荷

驚喜的是:模型也有類似問題。LLM 畢竟不是人,但它也有有限的「注意力」與上下文視窗。如果你要 GPT 在一次請求中:

  • 了解收禮者的一切,
  • 決定預算,
  • 考慮運送細節,
  • 找出 10 個選項,
  • 解釋為什麼選這些,

那模型會在每個子任務上消耗注意力與 token。一次請求中任務越多,某些部分就越可能粗糙或出錯。

若改為構建步驟鏈——本質上是把 chain-of-thought 外顯在介面上——模型先解決「擷取個人檔案」、再「校正預算」、再「挑選候選」。模型在每一步的推理品質通常更好。

可維護性與除錯

當一切被塞進單一工具與單一畫面,除錯就變成解謎:「到底在哪一步變糟了?」

在多步驟 workflow 中,你幾乎自動就擁有:

  • 可記錄的節點:step_startedstep_completedstep_failed
  • 清楚的轉換量測點(有多少人走到第 3 步),
  • 局部化問題:「只在產生點子那一步會炸」。

這些在 workflow 分析模組會派上用場,但現在就該養成以步驟思考的習慣。

4. workflow 中的步驟類型與 UI 呈現方式

我們已討論為何要切步驟。接下來整理常見的「積木」:避免變成雜亂的畫面集合,最好有一個步驟類型的「庫」。在你的 App 中幾乎總會重複幾個典型樣式。

以下是一張基本表:

步驟類型 目標 在 ChatGPT App 中常見的樣貌 GiftGenius 範例
資料蒐集(Wizard) 把複雜資料分段填寫 小型表單、chips、選項選擇、進度指示 「送給誰?」「年齡?」「興趣?」
分支 決定接下來走哪一條路 聊天中的提問 + UI 中的簡易選項 「禮物給小孩 → 兒童分類」
檢視/確認 讓使用者核對結果 摘要卡片 + 「返回」/「確認」按鈕 「這是我對她的理解,是否正確?」
最終步驟 結束情境並提供下一步行動 最終結果畫面 + 聊天中的 follow‑up 「這是你的禮物,想要下單嗎?」

要記得:同一個邏輯步驟可以以 UI 呈現,也可以是純文字對話。例如,「蒐集興趣」這個步驟可以是:

  • 表單與標籤:「運動」「桌遊」「烹飪」,
  • 或是一段對話,GPT 禮貌地追問:「他平常的興趣是什麼?」。

常見最佳解是混合式:GPT 發問、使用者以文字回答,同時也能在 widget 裡點擊 chips。

5. 誰來「帶」workflow:GPT、widget,還是伺服器?

直覺上你可能會說:「當然是 widget,我們是前端,用 state 控制一切。」但在 ChatGPT App 世界並非如此。Workflow 是三方共同合作。

作為協調者的 GPT

GPT:

  • 帶領對話、提出問題,
  • 決定什麼時候可以視為步驟完成,
  • 選擇何時呼叫 tool(例如「該產生禮物清單了」)。

對 GPT 而言,你的 workflow 是一組子任務。你能在 system‑prompt 中描述有哪些子任務以及通常的順序,但仍保留模型一定的調整空間。

GiftGenius 的 system‑prompt 內部小指令範例(偽代碼,非精確語法):

1. 先釐清收禮者的個人檔案(年齡、關係、興趣)。
2. 接著釐清預算。
3. 當資料足夠時,呼叫工具 suggest_gifts。
4. 拿到候選後,協助使用者做出選擇。

重點:GPT 不知道(也不該知道)你的 React 元件細節。它用目標導向的步驟來思考:「蒐集個人檔案」「挑選點子」。

作為步驟「門面」的 widget

Widget:

  • 只顯示當下最相關的步驟,
  • 保存 UI 狀態(被選的卡片、開啟中的分頁、表單的本地欄位),
  • 可以顯示步驟進度指示。

UI workflow 的最簡表示如下:

type GiftWorkflowStep =
  | "profiling"
  | "budget"
  | "candidates"
  | "checkout";

type GiftWidgetState = {
  step: GiftWorkflowStep;
  selectedGiftId?: string;
};

在 React widget 內,你可以用一般的 useState 儲存這個狀態,或若想綁定到 ChatGPT 中 widget 的生命週期,使用 Apps SDK 的 useWidgetState

const [widgetState, setWidgetState] = useState<GiftWidgetState>({
  step: "profiling",
});

widget 中的處理函式不會直接「購買禮物」,而是切換步驟,並把需要的資料回傳給模型/後端。

MCP-tools 作為 workflow 的「雙手」

MCP‑伺服器:

  • 保存商務狀態(個人檔案、選擇歷史),
  • 驗證步驟(「沒有選定禮物就不能進入 Checkout」),
  • 執行繁重工作:目錄檢索、價格計算、與 ACP 整合。

例如,決定「要顯示哪些禮物」比較適合由 MCP‑tool suggest_gifts 來做,讓模型能在釐清條件時多次呼叫。

於是你得到清楚的分工:

  • GPT —— 文字互動與順序,
  • widget —— 當前步驟的視覺呈現,
  • MCP —— 資料與不變條件。

6. 如何在程式碼中描述 workflow:迷你 state‑machine

還記得開頭的 GiftGenius 狀態圖嗎?現在把同樣的邏輯寫成簡單的型別與函式——程式碼中的迷你 state‑machine。我們不打算把你的 App 變成理論課,但幾個型別與函式能大幅簡化工作。

步驟型別與設定

先以宣告式方式描述步驟。拿先前的 GiftWorkflowStep(為了清楚起見再重複一次),並為它定義設定:

type GiftWorkflowStep =
  | "profiling"
  | "budget"
  | "candidates"
  | "checkout";

type StepConfig = {
  label: string;
  isFinal?: boolean;
};

export const GIFT_WORKFLOW_STEPS: Record<GiftWorkflowStep, StepConfig> = {
  profiling: { label: "收禮者" },
  budget: { label: "預算" },
  candidates: { label: "選項" },
  checkout: { label: "結帳", isFinal: true },
};

接著加上一個簡單的轉移函式:

export function getNextStep(
  current: GiftWorkflowStep
): GiftWorkflowStep | null {
  switch (current) {
    case "profiling":
      return "budget";
    case "budget":
      return "candidates";
    case "candidates":
      return "checkout";
    default:
      return null; // 結尾
  }
}

這已經帶來:

  • 集中管理的步驟清單,
  • 明確的轉移規則,
  • 能快速調整順序與邏輯的能力。

在 widget 中使用

最簡版的「精靈」在你的 widget 中可能長這樣:

function GiftWizard() {
  const [step, setStep] = useState<GiftWorkflowStep>("profiling");

  const handleStepComplete = () => {
    const next = getNextStep(step);
    if (next) setStep(next);
  };

  return (
    <div>
      <ProgressBar step={step} />
      <StepContent step={step} onComplete={handleStepComplete} />
    </div>
  );
}

StepContent 元件能依據步驟渲染不同子表單:

function StepContent(props: {
  step: GiftWorkflowStep;
  onComplete: () => void;
}) {
  const { step, onComplete } = props;

  if (step === "profiling") {
    return <ProfilingStep onNext={onComplete} />;
  }
  if (step === "budget") {
    return <BudgetStep onNext={onComplete} />;
  }
  if (step === "candidates") {
    return <CandidatesStep onNext={onComplete} />;
  }
  return <CheckoutStep />;
}

注意:這裡我們暫時不管 GPT 如何選擇步驟——這只是本地的 UI 邏輯。稍後你可以把這個 step 與伺服器狀態或 tools 訊息同步,但理解多步驟概念,這樣就足夠了。

7. 擴充教學應用:從「超級表單」到精靈

假設在這堂課之前,你的 GiftGenius‑widget 是個「大表單」:

  • 收禮者姓名,
  • 年齡,
  • 興趣,
  • 預算,
  • 場合類型,
  • 勾選「需要配送」與另外五個欄位,
  • 底部只有一個大按鈕「挑選禮物」。

做原型時這常常沒問題,但只要想走向產品化——就該切成步驟。

「之前」長這樣

誇張一點的例子:

// 反樣式:一個巨大的表單
function GiftFormAllInOne() {
  return (
    <form>
      {/* 10+ 個欄位混在一起 */}
      {/* ... */}
      <button type="submit">挑選禮物</button>
    </form>
  );
}

常見問題:

  • 使用者不清楚哪些欄位是必填,
  • 不清楚需要花多少時間,
  • 更難讓 GPT 向使用者解釋發生了什麼,並做後續引導。

如何做成「之後」:三個畫面的精靈

步驟 1——把個人檔案與預算分開:

function ProfilingStep(props: { onNext: () => void }) {
  const [recipientType, setRecipientType] = useState("");
  const [interests, setInterests] = useState<string[]>([]);

  const handleSubmit = () => {
    // 這裡可以呼叫儲存個人檔案的 tool
    props.onNext();
  };

  return (
    <div>
      <h3>我們要為誰找禮物?</h3>
      {/* 用於類型與興趣的單選鈕/chips */}
      <button onClick={handleSubmit}>下一步</button>
    </div>
  );
}

步驟 2——預算:

function BudgetStep(props: { onNext: () => void }) {
  const [budget, setBudget] = useState<number | null>(null);

  const handleSubmit = () => {
    // 可以呼叫預算驗證的 tool
    props.onNext();
  };

  return (
    <div>
      <h3>你的預算是多少?</h3>
      {/* slider 或 input */}
      <button onClick={handleSubmit} disabled={!budget}>
        挑選候選
      </button>
    </div>
  );
}

步驟 3——候選清單:

function CandidatesStep(props: { onNext: () => void }) {
  const [selectedId, setSelectedId] = useState<string | null>(null);

  // 這裡顯示禮物卡片
  // 並允許選擇其中一個

  return (
    <div>
      <h3>選擇合適的選項</h3>
      {/* 卡片 onClick = setSelectedId */}
      <button onClick={props.onNext} disabled={!selectedId}>
        前往結帳
      </button>
    </div>
  );
}

沒錯,程式碼變多了,但邏輯更簡單:

  • 每個步驟都解決一個小問題,
  • 模型可以單獨評論步驟間的切換,
  • 你可以分別記錄/量測每個步驟。

8. 反樣式:別把 workflow 變成怪獸

實務與對類似 App 的觀察,顯示有幾個常見錯誤很值得避免。

首先,不要想用複雜的 BPMN 圖「畫滿一切」,裡面 30 個狀態、40 條箭頭、A0 大圖。在 ChatGPT App 的情境中,可直覺理解的階梯式步驟比形式化符號更重要。像我們替 GiftGenius 畫的那種圖就足夠了。

其次,別把 App 做成一個龐大的表單,尤其是在內嵌 widget 裡。使用者已經在聊天了;額外的 UI 模組應該降低、而非提高負荷。如果你發現自己在想「這 12 個欄位都很重要」——幾乎可斷定應該切步驟。

第三,別做「為了好看」而存在的步驟。每個步驟都該有明確目標:要不是蒐集資料,要不就是縮小選項,或是讓使用者確認。像「再一下就好了」這種只有「下一步」按鈕的空白畫面,幾乎沒幫助。

最後,不要一開始就想把 App 的所有能力全部曝光。像「進階篩選」「特殊配送條件」之類的細節,可以做成額外步驟,只對真的需要的人顯示。

9. 簡單的 workflow 設計練習

為了更好地鞏固內容,試著在紙上(或 IDE,但先不寫碼)做以下練習。

選一個任務,可能是:

  • 挑選禮物(GiftGenius),
  • 預訂旅程,
  • 制定某項技能的學習計畫。

把它切成 3–5 個步驟。對每個步驟描述:

  • 目標:完成該步驟後應該知道/完成什麼,
  • 形式:這裡比較適合純 GPT 文字、widget,或兩者結合。

例如,一個簡單的「TypeScript 學習計畫」:

  1. 步驟「程度評估」——對話(GPT 問幾個問題)+ 簡短自評表單。
  2. 步驟「目標」——文字討論 + widget 中的目標勾選框。
  3. 步驟「計畫」——產生計畫(清單)+ 按鈕「加難/變簡」。
  4. 步驟「確認」——簡短摘要與按鈕「保存計畫」。

接著試著想想,每個步驟可能會用到哪些 tools,但先別陷入細節:工具、它們的啟用/停用與狀態保存——會在本模組後續的講座中討論。

10. 多步驟 workflow 常見錯誤

錯誤 1:試圖用單一步驟與單一 tool 解決所有問題。
很容易想做一個「大型聰明工具」,既能發問、又能分析、還能自己挑選、甚至直接下單。實際上,這會同時惡化 UX(單一沉重畫面)與模型的推理品質(一次承擔太多任務)。把任務拆成 3–5 個簡單步驟更簡單、可靠,維運也更省力。

錯誤 2:隱性步驟只存在於開發者腦中。
有時程式中看似有一連串動作,但它從未被明確描述:沒有步驟型別、沒有設定、沒有圖。結果是,團隊裡誰都說不清「這個 App 從頭到尾到底發生了什麼」。最基本的宣告式步驟/轉移描述,能省下大量除錯時間。

錯誤 3:把 UI 步驟與商務邏輯混在一起。
若步驟轉移邏輯被埋進 React 元件深處(在 if (isValid && hasBudget && !needsShipping)onClick 之類),就很難重用與測試。最好有相對明確的「狀態機」或至少 getNextStep 函式,而 UI 只需呼叫並顯示結果。

錯誤 4:忽視 GPT 作為協調者的角色。
有時開發者想完全用 widget 控制情境:「我要自己問完所有需要的問題,模型只負責挑選。」結果 ChatGPT 看起來不像活生生的助理,而像表單背後的計算引擎。更好的體驗是,GPT 積極溝通、推動下一步,並主動發起 tools 呼叫——你則用步驟設計與指令協助它。

錯誤 5:沒有明確目標的步驟。
有時精靈裡會出現「多餘」的步驟——老實說,可能只是設計看起來比較漂亮。使用者看到「步驟 2/5」,但那一步既不需要他做什麼,也沒有任何進展。這種空白畫面只會增加複雜感。如果一個步驟無法表述為「之後我們確定了 X」或「之後使用者完成了 Y」,那它多半是不需要的。

錯誤 6:忘了進度,讓使用者缺乏路徑感。
沒有視覺化輔助的多步驟,會變成一個黑盒子:使用者不知道自己在哪、還剩多少。即使只是簡單的文字指示「步驟 2/4」,或在 widget 頂部水平列出步驟,都能大幅降低焦慮。忽略這點,是人們「半路離開」的原因之一,即便實際的複雜度可能不高。

留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION