1. 在 ChatGPT App 背景下,什么是 workflow
如果你已经搞定了授权,那就值得表扬。我们来进入一个非常有趣的话题——ChatGPT App 里的 workflow。说到“workflow”,很多人会被 BPMN 图和沉闷的企业软件触发回忆。别担心:在 ChatGPT App 的语境下,我们关注的是更轻量的版本。
在本课程中,workflow 指的是一个多步骤的场景,其中:
- 有清晰的目标(例如挑选礼物并推进到购买),
- 有连续的步骤(问询 → 生成备选 → 细化 → 完成),
- 在每个步骤里,GPT、小部件和工具各司其职。
关键点:workflow 并不是“MCP 服务器里的一个方法”。它是一个组合:
- 模型的推理(问哪些问题、在何时调用哪个工具),
- tools 的调用(MCP/Agents),
- 小部件中的 UI 步骤,
- 后端的状态。
也就是说,你不需要一个“万能工具” solve_everything,而是需要若干简单工具,在不同阶段启用。同样,也不是一个“超级小部件”,而是一组小屏/状态,每个解决一个子任务。
workflow 中的“责任三角”
把 workflow 想成三个参与者的配合舞最为方便:
| 角色 | 在 workflow 中的职责 | GiftGenius 中的示例 |
|---|---|---|
| GPT | 大脑。理解用户意图,决定何时步骤完成、下一步是什么。可以调用 tools。 | 理解“我想给极客买点什么”,并决定调用 search_items(category="geek")。 |
| Widget | 界面。渲染当前步骤,只展示相关内容,收集点击与输入。保存 UI 状态。 | 先显示“送给谁的礼物?”的表单,然后是礼物卡片,最后是“购买”按钮。 |
| MCP/Agent | 双手。执行繁重且结构化的工作,校验数据,保存业务状态。 | 保存收礼人档案,请求礼物目录,并按预算过滤。 |
这三种角色共同实现同一个场景,但作用层级不同:GPT 决定“下一步是什么”,小部件展示“现在是什么”,MCP 负责数据层面真正发生了什么。
2. 基于 GiftGenius 的 workflow 示例
我们用熟悉的 GiftGenius 场景——礼物挑选助手。它可以被描述为一个简单的线性向导。
步骤顺序可能是:
- 收集收礼人的基础信息。
- 确定预算与约束。
- 生成并过滤礼物创意。
- 展示候选项,允许点赞/隐藏。
- 进入结算(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 个字段放在一个屏幕上,几乎可以保证“愤然退出”:用户放弃并直接关掉标签页。用户来这里是为了“对话”,不是为了“考试”。
如果你把任务拆成步骤:
- “第 1 步(共 4 步):请介绍这位收礼人”,
- “第 2 步(共 4 步):选择预算”,
- “第 3 步(共 4 步):查看候选项”,
- “第 4 步(共 4 步):确认选择”,
那么每个时刻都显得可完成。进度条或步骤标签会带来掌控感:清楚发生了什么、还剩多少。
模型的认知负荷
惊喜的是:模型也有类似的问题。LLM 当然不是人,但它也有有限的“注意力”和上下文窗口。如果你让 GPT 在一次请求中同时:
- 问清关于收礼人的一切,
- 搞定预算,
- 考虑配送细节,
- 挑出 10 个备选,
- 解释为什么是这些选项,
那么模型会把注意力与 token 分散在每个子任务上。在一个请求里任务越多、越不相干,就越可能出现浅尝辄止或出错。
而如果你把流程做成步骤链——本质上就是把 chain-of-thought 显式地铺在界面里——模型先解决“抽取档案”的窄任务,再“校准预算”,然后“挑选候选项”。模型在每个环节的推理质量会明显更高。
可维护性与调试
当一切都塞进一个工具和一个屏时,调试就变成了“到底哪里出了问题”的寻宝游戏。
在多步骤的 workflow 中,你几乎自带:
- 日志点:step_started、step_completed、step_failed,
- 清晰的转化测点(有多少人走到了第 3 步),
- 定位问题的能力:“只在生成创意这一步会失败”。
这些会在 workflow 分析模块中继续用到,但从现在开始就请养成“按步骤思考”的习惯。
4. workflow 中的步骤类型以及它们在 UI 中的样子
我们已经讨论了为什么要拆步。现在来梳理步骤本身,看看在 ChatGPT App 中最常见的“基石”有哪些。为了不陷入杂乱的屏幕堆,建立一套“步骤类型库”很有用。你的 App 里经常会反复出现几个模式。
这里是一张基础表:
| 步骤类型 | 目标 | 在 ChatGPT App 中通常的样子 | GiftGenius 中的示例 |
|---|---|---|---|
| 数据采集(Wizard) | 分步填充一个复杂对象 | 小表单、标签(chips)、选项选择、进度指示 | “送给谁?”、“年龄?”、“兴趣?” |
| 分支 | 决定接下来走哪条路径 | 聊天中的一个问题 + UI 中的简单选项 | “给孩子的礼物 → 儿童分类” |
| 查看/确认 | 让用户核对最终结果 | 汇总卡片 + “返回”/“确认”按钮 | “我对她的理解如下,是否正确?” |
| 最终步骤 | 完成场景,并提出后续动作 | 带有结果的最终屏 + 聊天里的 follow‑ups | “这是你的礼物列表,要去下单吗?” |
需要记住:同一个逻辑步骤既可以在 UI 中体现,也可以只是纯文本对话。例如,“收集兴趣”这一步可以是:
- 一个带“运动”“桌游”“烹饪”等标签的表单,
- 或者一段对话,GPT 温和地追问:“他/她平时有哪些爱好?”。
很多时候,最佳方案是混合式:GPT 提问,用户用文本回答,同时也可以在小部件里点击标签。
5. 谁来“驱动” workflow:GPT、小部件还是服务器?
直觉可能会说:“当然是小部件,我们做前端的,一切都用 state 控制。”但在 ChatGPT App 世界里并非如此。Workflow 是三方协作的结果。
GPT 作为编排者
GPT:
- 进行对话,提出问题,
- 决定何时可以认为步骤完成,
- 选择何时调用 tool(例如,“该生成礼物建议了”)。
对它而言,你的 workflow 是一组子任务。在 system‑prompt 里,你可以描述有哪些子任务、通常以什么顺序执行,但要保留一定自由度让模型做些微调整。
GiftGenius 的 system‑prompt 中的一个迷你指令示例(伪代码,非精确语法):
1. 先澄清收礼人的档案(年龄、关系、兴趣)。
2. 再确认预算。
3. 当数据足够时——调用工具 suggest_gifts。
4. 拿到候选项后——帮助用户做选择。
重点:GPT 不知道(也不需要知道)你的 React 组件细节。它用“收集档案”“生成创意”这样的目标语义来思考步骤。
小部件作为步骤的“界面”
小部件:
- 只展示当前相关的步骤,
- 保存 UI 状态(选中的卡片、打开的标签、本地表单字段),
- 可以展示步骤进度指示器。
在代码中对 UI‑workflow 的最简单表达:
type GiftWorkflowStep =
| "profiling"
| "budget"
| "candidates"
| "checkout";
type GiftWidgetState = {
step: GiftWorkflowStep;
selectedGiftId?: string;
};
在 React 小部件内部,你可以用普通的 useState 保存这个状态,或者如果你想绑定到 ChatGPT 中小部件的生命周期,可以用 Apps SDK 的 useWidgetState。
const [widgetState, setWidgetState] = useState<GiftWidgetState>({
step: "profiling",
});
小部件中的处理函数不会直接“购买礼物”,而是修改步骤并把所需数据回传给模型/后端。
MCP-tools 作为 workflow 的“双手”
MCP 服务器:
- 保存业务状态(档案、选择历史),
- 校验步骤(“没有选中礼物就不能进入 Checkout”),
- 执行重活:目录搜索、价格计算、与 ACP 集成。
例如,“展示哪些礼物”的决定逻辑更适合放在 MCP‑tool suggest_gifts 中,这样模型在细化条件时可以多次调用它。
于是你得到清晰的分工:
- GPT——文本与顺序,
- 小部件——当前步骤的可视化呈现,
- MCP——数据与不变条件。
6. 如何在代码中描述 workflow:迷你 state‑machine
还记得开头 GiftGenius 的状态图吗?现在把同样的逻辑写成简单的类型与函数——代码里的迷你状态机。我们不会把你的 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; // 结束
}
}
这已经带来:
- 集中化的步骤清单,
- 显式的跳转规则,
- 快速修改顺序与逻辑的能力。
在小部件中使用
一个最简版的“向导”在你的小部件里可以是这样:
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 小部件是一份“超大表单”:
- 收礼人姓名,
- 年龄,
- 兴趣,
- 预算,
- 事件类型,
- 勾选“需要配送”和另外五个字段,
- 底部一个大按钮“挑选礼物”。
做原型时这经常没问题,但一旦你想做成产品流程——就该拆成步骤了。
“之前”的样子
夸张点的例子:
// 反模式:一个巨大的表单
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>
{/* 一组单选/标签用于类型与兴趣 */}
<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>
{/* 滑块或输入框 */}
<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 变成一个巨型表单,尤其是在内联小部件里。用户已经在聊天里;加入的 UI 区块应该降低而不是增加负担。如果你发现自己在想“这 12 个字段都很重要”——这几乎总是需要拆分任务的信号。
第三,别做“为了好看”的步骤。每一步都应有清晰目标:要么收集数据,要么缩小范围,要么让用户确认。像“再等等就好了”且只有一个“下一步”按钮的空屏通常没有帮助。
最后,别在初始步骤就把 App 的所有能力都展示出来。诸如“高级筛选”“特殊配送条件”等细节,应该只对确实需要的人作为额外步骤按需开启。
9. 设计 workflow 的简单练习
为了更好地巩固内容,试着在纸上(或 IDE 中,但先别写代码)做下面的练习。
取一个任务。比如:
- 挑选礼物(GiftGenius),
- 预订旅行,
- 制订某项技能的学习计划。
把它拆成 3–5 个步骤。对每一步,写下:
- 目标:在这一步之后应该知道/完成什么,
- 形式:这里更适合纯 GPT 文本、小部件,还是二者结合。
例如,一个简单的“TypeScript 学习计划”:
- 步骤“水平评估”——对话(GPT 提几个问题)+ 一个自评的短表单。
- 步骤“目标”——文本讨论 + 小部件里的目标复选框。
- 步骤“计划”——生成计划(列表)+ “更难/更易”的按钮。
- 步骤“确认”——简短总结与“保存计划”按钮。
然后再想想各步骤可能会用到哪些 tools,但先别陷入细节:工具、启用/禁用与状态持久化会在本模块的后续讲里展开。
10. 处理多步骤 workflow 时的常见错误
错误 1:尝试用一个步骤和一个 tool 解决所有问题。
做一个“万能大工具”很诱人:它既提问、也分析、还自己挑选并下单。实际上这会同时降低 UX(一个沉重的大屏)和模型的推理质量(reasoning)——一次调用里职责太多。把任务拆成 3–5 个简单步骤的链路更易用、更可靠、维护成本更低。
错误 2:步骤是隐性的,只存在于开发者脑中。
有时代码里似乎有一串动作,但并没有显式描述:没有步骤类型、没有配置、没有图。结果团队里没人能清晰回答“这个 App 从头到尾发生了什么”。最小化的声明式步骤与跳转描述能省下大量调试时间。
错误 3:把 UI 步骤与业务逻辑混在一起。
如果步骤跳转逻辑深埋在 React 组件里(比如在按钮 onClick 里写一堆 if (isValid && hasBudget && !needsShipping)),就很难复用和测试。最好有一个相对显式的“状态机”甚至只是一组 getNextStep 函数,UI 只负责调用与展示结果。
错误 4:忽视 GPT 的编排者角色。
有时开发者试图完全从小部件控制流程:“我自己把该问的都问了,让模型只做挑选”。结果 ChatGPT 不再像一个活的助手,而像一个表单下的计算引擎。更好的体验是:GPT 主动交流、推动下一步并主动发起 tools 调用——而你通过步骤设计与指令来帮助它。
错误 5:没有清晰目标的步骤。
有时向导里会出现“多余”的步骤——说白了只是因为“看起来更美”。用户看到“第 2 步(共 5 步)”,但这一步并不需要他做什么,也没有发生什么。这类空屏只会增加复杂感。如果一个步骤无法表述为“之后我们明确知道了 X”或“之后用户完成了 Y”,那它很可能不需要。
错误 6:忘了进度,缺乏路径感。
没有可视化支撑的多步骤会变成黑盒:用户不知道自己在哪、还剩多少。即便只是简单的“第 2 步(共 4 步)”文本指示,或小部件顶部的水平步骤列表,也会明显降低焦虑。忽视这一点是用户在流程中途“流失”的原因之一,尽管那里的实际难度可能并不大。
GO TO FULL VERSION