AI Native 交互范式
AI Native 不是在每个页面塞一个对话框。真正的公式是:意图输入 + 过程可见 + 结果可干预——对话框只是其中一种载体,有时甚至是最差的那个。
先破个题:对话框不是答案
过去两年,无数产品的「AI 化」等于在右下角加一个聊天气泡。问客服是聊天,写文档是聊天,数据分析也是聊天——仿佛对话框是 AI 的唯一合法形态。
但用得多了你会发现:有些东西用对话做,比原来的 GUI 还难用。想给表格按时间排序,原来点一下列头;现在要打一句「帮我把这个表格按创建时间倒序排列」,等三秒,然后祈祷它没理解错。这不是进步,是倒退。
AI Native 的交互设计,核心不是「加个对话框」,而是重新回答三个问题:
- 意图输入:用户怎么告诉系统「我想要什么」——对话只是方式之一
- 过程可见:AI 干活的几秒到几小时里,用户能不能看到它在干嘛
- 结果可干预:用户能不能中途叫停、修改、审批,而不是只能全盘接受或重来
这一章就按这三件事展开:什么时候该用对话、什么时候对话框是灾难;什么时候模型该直接生成界面;流式输出背后的体验工程学;后台跑长任务时 UI 该怎么办;最后用微软的 Human-AI 交互准则把这些散点串成体系。
对话式 UI 的边界
对话擅长一件事:表达模糊、探索性的意图。「帮我看看这个月销量下滑的原因」——你脑子里没有明确路径,需要一来一回地澄清,对话天然合适。写作初稿、头脑风暴、陌生领域的问答,都是对话的主场。
对话不擅长的事也很明显:精确、高频、可预期的操作。举几个对话框堪称灾难的真实场景:
- 计算器 / 单位换算:「35 美元是多少人民币」在搜索框里早就是即时答案,让 LLM 算既慢又可能算错。输入框 + 实时结果完胜对话框
- 电商筛选:「价格 200-500、包邮、评分 4.5 以上、明天能到」——这是一组离散条件的组合,勾选筛选器比把它写成一句自然语言快得多,且可以随时单独改一项。对话里改一项要重述整句话
- 视频剪辑 / 音频编辑:把时间轴上 00:32 到 00:45 这一段删掉——拖拽选区是直觉操作,用语言精确描述时间点纯属自虐
- 数据看板:用户要的是「每天扫一眼」的被动监控,对话是主动查询,范式本身就是拧的
- 打车软件:起点终点是地图上的两个点,地图选点的信息密度远高于文字描述地址
看出规律了吗?这些场景的共同点:意图是精确的、结构化的,且操作会反复发生。自然语言的强项是压缩模糊意图,弱项恰恰是表达精确结构——而 GUI 控件(滑杆、勾选框、地图、时间轴)就是为精确结构而生的编码器。
一个好的判断框架:
- 意图越模糊,对话越值(探索、问答、创作初稿)
- 操作越精确,GUI 越值(调整参数、批量处理、浏览结构化数据)
- 频率越高,越该从对话固化成控件:第一次用对话发现「这个筛选组合好用」,第十次就该给它做个按钮了
- 最好的形态是混合:对话负责「定方向」,GUI 负责「微调」。Cursor 就是这个模式的教科书——你用对话描述要改什么,它给你 diff,你用按钮逐个接受或拒绝。对话和 GUI 各干各擅长的
争议:对话是未来,还是过渡形态?
这个话题业内吵得很凶,两边都有像样的论据:
正方(对话是未来):自然语言是带宽最高的意图通道,GUI 只是算力不足时代的妥协——按钮和菜单本质上是「把意图预先枚举成有限集合」,而真实意图是无限的。模型理解力越强,需要枚举得越少,最终一切归于对话。语音成熟后这个趋势只会加速。
反方(对话是过渡形态):对话有三大硬伤——不可发现(用户不知道能问什么,空输入框是最差的引导)、不可复现(同一句话两次结果可能不同,而按钮有肌肉记忆级的确定性)、高表达成本(打字永远比点击慢)。人类用了四十年 GUI 不是因为算力不够,是因为指向(pointing)本身就是比描述(describing)更底层的认知方式——你指着一个东西说「把这个改一下」比纯语言描述省力得多。
我的看法:两边说的不是同一层。对话吃掉的是「意图层」,GUI 守住的是「控制层」。未来的界面大概率是「对话提出、GUI 确认」的混合体——而「确认」那一半,就是下面要讲的生成式 UI。
生成式 UI:模型直接生成界面
对话输出的是文字,但很多问题的答案天生不该是文字。问「各季度营收对比」,最好的回答是一张图表;问「帮我挑个航班」,最好的回答是可点击的卡片列表。
Generative UI 的思路是:模型不输出最终渲染结果,而是输出一份结构化的「界面描述」,前端拿到描述后渲染成真实组件。
模式一:组件白名单 + JSON Schema
关键约束是组件白名单——模型只能从你预先定义的组件库里挑,参数必须符合 schema,绝不让模型直接吐 HTML 或任意代码。一个最小的 TypeScript 实现:
// 模型只能输出这几种组件描述,用 tool_call 的参数 schema 做硬约束
type GenUI =
| { type: "chart"; data: { label: string; value: number }[] }
| { type: "table"; rows: string[][] }
| { type: "text"; body: string };
function renderUI(ui: GenUI) {
switch (ui.type) {
case "chart":
return <BarChart data={ui.data} />;
case "table":
return <DataTable rows={ui.rows} />;
case "text":
return <p>{ui.body}</p>;
}
}
为什么必须是白名单而不是任意代码?三个理由:安全(模型吐的 HTML 可能带脚本注入)、样式一致性(设计系统的控制权在你手里,不在模型手里)、可干预(白名单组件你知道它有哪些交互能力,可以预埋按钮和回调)。
模式二:流式部分渲染
生成式 UI 和流式输出是可以叠加的,但有个技术难点:流式到达的是半个 JSON,直接 JSON.parse 必炸。工程上有两种解法:
- 容忍解析:用 partial-json 这类库解析不完整的 JSON,已完整的字段先渲染,缺的字段显示骨架屏。用户看着图表的坐标轴先出来、数据柱一根根长出来
- 分段协议:不让模型吐一整块 JSON,而是按组件分段输出(比如每个组件一个独立的 tool_call),收到一段渲染一段。Vercel AI SDK 走的就是这条路
Vercel AI SDK 的机制
值得单独说一下这个参考实现,因为它把上面的模式做成了标准能力。核心机制(以 streamUI 为代表):
- 服务端定义工具集,每个工具的参数用 Zod schema 描述,同时绑定一个 生成器函数,返回 React 组件
- 模型流式输出 tool_call,SDK 在流里实时解析,参数一完整就调用生成器,把组件本身(而不是文本)作为流的一部分推给前端
- 基于 React Server Components 序列化,组件可以携带交互能力(按钮、回调)直达客户端
- 前端拿到的是一个「文本和组件混排」的流,按到达顺序渲染
这套机制的价值在于把「模型决定用什么组件」和「组件怎么实现」彻底解耦:模型只懂 schema,不懂 React;组件只懂 props,不懂模型。换模型不动 UI,改 UI 不动模型。
失败降级矩阵
生成式 UI 的每一环都可能失败,设计时必须逐层想好退路:
| 失败点 | 典型原因 | 降级策略 |
|---|---|---|
| schema 校验失败 | 模型幻觉出不存在的字段 | 丢弃该组件,重试一次或直接退化成纯文本 |
| chart 渲染失败 | 数据为空、数值越界 | 退化成 table 展示同样的数据 |
| table 渲染失败 | 行列结构异常 | 退化成 markdown 纯文本 |
| 组件流中断 | 网络断开、生成超时 | 已渲染部分保留 + 断点提示 + 重试按钮 |
| 白名单外组件 | 模型编造组件名 | 静默替换为 text,记录日志用于迭代白名单 |
铁律只有一条:任何情况下用户都要能看到内容,最差也是一段纯文本回答。白屏是生成式 UI 唯一不可接受的结局。
流式响应:打字机效果背后的心理学
为什么 AI 产品都爱用「打字机」式的逐字输出?不是因为模型只能一个字一个字地吐——完全可以等生成完再一次性展示。真正的原因是感知性能(perceived performance):用户体验到的速度,比客观速度更重要。
阈值的原始出处
业内常引用的「0.1 / 1 / 10 秒」三条线,原始出处是 Jakob Nielsen 1993 年的《Usability Engineering》第五章,而 Nielsen 的源头又可以追到 Robert B. Miller 1968 年的论文《Response Time in Man-Computer Conversational Transactions》。三条线的含义:
- 0.1 秒:用户感觉界面是「即时」的,操作和反馈融为一体
- 1 秒:用户的思路不被打断的上限,超过了就需要反馈来「接住」注意力
- 10 秒:用户注意力能留住的上限,超过就必须给进度反馈,否则人会走
有意思的是,这套阈值来自大型机时代,硬件换了几茬,数字却经久不衰——因为它度量的不是机器,是人类注意力的生理常数。LLM 生成一段像样回答通常要 5-30 秒,远超 10 秒红线,这就是为什么流式几乎是所有 AI 产品的标配。
流式为什么有效
流式输出把「等待」变成了「阅读」:第一个 token 往往在 1 秒内到达,用户从那一刻起就有东西可看,主观等待时间大幅缩短。而且逐字出现的内容自带一种「它在认真工作」的暗示——这比转圈圈的 loading 动画诚实得多,因为输出本身就是进度。
流式的最小实现(浏览器端,SSE):
const res = await fetch("/api/chat", {
method: "POST",
body: JSON.stringify({ prompt }),
});
const reader = res.body!.getReader();
const decoder = new TextDecoder();
let acc = "";
while (true) {
const { done, value } = await reader.read();
if (done) break;
acc += decoder.decode(value, { stream: true });
setText(acc); // 每到一个 chunk 就更新 UI
}
TTFT 优化清单
流式体验的核心指标是首 token 延迟(TTFT),而不是总耗时——用户的耐心是在前 10 秒消耗掉的。优化手段按成本从低到高:
- 先流「思考状态」再流内容:UI 上一收到请求立刻切「正在分析…」,这是零成本的感知优化
- 缩短 prompt:系统提示词和塞进的上下文每多 1k token,TTFT 就实实在在地涨
- 命中 KV 缓存:把不变的前缀(系统提示、工具定义)放在 prompt 最前面,提高前缀缓存命中率
- 选小模型做前奏:简单问题路由到小模型,复杂问题才上大模型
- 就近部署 / 选近端接入点:跨国网络往返就是几百毫秒
- 连接预热:页面加载时就建立好到推理服务的连接,别等用户点了按钮再握手
- 投机解码(speculative decoding):推理侧用小模型起草、大模型验证,直接降低每 token 延迟
- 边生成边渲染 markdown:流式渲染要支持不完整语法(一个没闭合的加粗不能炸掉整段)
体验设计的四个细节
- 必须能中途取消:AbortController 断掉请求,同时后端也要真的停止生成,否则用户省的是时间,你烧的是 token 钱
- 不要用假流式:有些产品拿到完整结果后故意逐字播放「模拟」打字机,一旦被发现(中途没法取消、永远匀速),信任直接归零。要么真流式,要么老实转圈
- 给「思考中」一个独立的阶段:带 reasoning 的模型可能先静默思考十几秒再输出,这段时间需要明确的状态提示,否则用户以为卡死了
- 流完要有「完成态」:输出停止和输出完成是两回事,完成后给出明确收尾(复制/重新生成按钮亮起),别让光标一直闪
后台 Agent 与异步任务:任务跑几小时,UI 怎么设计
前面的场景都假设用户在线等。但 Agent 真正有价值的场景——深度调研、批量处理、代码迁移——动辄几十分钟到几小时。没人会盯着一个页面等两小时,此时 UI 的本质从「聊天窗口」变成了「项目管理工具」。
任务状态机:完整设计
设计这类界面,先想清楚任务的完整生命周期,并用状态机显式建模:
type TaskState =
| { status: "queued" }
| { status: "running"; step: string; percent: number }
| { status: "waiting_approval"; draft: string } // 检查点:等人拍板
| { status: "paused" }
| { status: "done"; result: string }
| { status: "failed"; error: string; resumeFrom: string }
| { status: "cancelled" };
合法的迁移路径要显式定义,不允许乱跳:
注意 waiting_approval 是一等公民,不是 running 的子状态。原因很简单:它和 running 的 UI 完全不同(前者需要醒目的审批入口,后者只需要进度展示),通知策略也不同。把它并进 running,代码里就会到处长 if 分支。
通知策略:分级触达
任务在后台跑,人就走了。通知是把用户「叫回来」的机制,但不能什么都推——通知通货膨胀的结果是全被关掉。按打扰程度分级:
- waiting_approval:强提醒(应用内角标 + IM webhook / 邮件)。用户在阻塞任务,越早回来越好。内容带摘要和直达链接,点一下落到审批界面
- failed:立即通知。带失败原因和「从断点重试」按钮,不要只甩一句「任务失败」
- done:弱提醒(应用内通知即可,不打扰)。带结果摘要,让用户决定要不要细看
- running:不通知,进度变化只更新在任务面板里,主动查看而不是被动推送
恢复语义:失败必须可恢复
跑了 50 分钟挂了,用户最恨的是「从头再来」。恢复能力是个工程组合拳:
- 幂等:子任务重复执行不产生重复副作用(写结果前先查是否已写,或用幂等键去重)
- 检查点持久化:每完成一个子任务,把
resumeFrom指向的进度写进数据库——不能只在内存里,服务重启、用户刷新都不能丢 - 副作用与状态分离:已经发出的邮件、已经提交的 PR 是「真实世界的副作用」,恢复时必须先查副作用记录再决定是否重做,否则重试=重复发邮件
- 终态补偿:
cancelled不是简单停掉,已完成的副作用要不要回滚,得逐个定义(能撤销的撤销,不能的在报告里如实列出)
检查点审批:用可干预换信任
跑两小时全自动听起来美,实际是「两小时后给你一个大惊喜/惊吓」。好的设计是在关键决策点(比如「计划修改 12 个文件,是否继续?」)暂停,把草案/diff 摆出来等人审批。审批界面给的不该是「是/否」两个按钮,而是完整的上下文:要改什么、为什么、改完之后是什么样(diff 预览)。用户批准的是理解之后的方案,不是开盲盒。
看 Claude Code、Devin 这类产品的界面:左边是任务列表,右边是当前任务的实时日志和 diff,顶部是审批按钮——没有一个元素是传统聊天框,但意图输入、过程可见、结果可干预三样一样不少。这就是 AI Native 交互的成熟形态。
用 Human-AI 交互准则收个尾
上面这些散点经验,其实早有体系化的总结。微软研究院 2019 年在 CHI 上发表的《Guidelines for Human-AI Interaction》,从 150 多篇文献和 20 多轮验证中提炼出 18 条准则。挑和本章最相关的四条展开:
G1:明确系统能做什么(Make clear what the system can do)。 空输入框是对话式 UI 最大的反模式——用户不知道能问什么,试探三次失败后就把功能判了死刑。落地做法:输入框里放会轮换的示例提示、首次使用给三个可点的能力卡片、做不到的事明确说「这个我不会」。能力边界本身就是 UI 的一部分。
G9:支持高效的纠正(Support efficient correction)。 AI 会错,这是前提不是意外,所以「纠正」必须是和「生成」同级的一等交互。落地做法:每个结果旁边有「不对」的低成本反馈入口;局部可改(重新生成这一段,而不是整个重来);纠正后的偏好要记住(用户改了三次标题风格,第四次就该主动对齐)。
G11:说明行为的原因(Make clear why the system did what it did)。 过程可见的理论版。落地做法:引用来源(这句话来自哪个文档)、展示推理的关键步骤(不是全文思维链,是可读的摘要)、审批点解释「为什么需要你拍板」。解释不是免责声明,是让用户建立准确心智模型的材料。
G17:提供全局控制(Provide global controls)。 用户要能全局性地约束 AI 的行为,而不是每次任务单独叮嘱。落地做法:全局的「自动执行到什么程度」开关(只建议 / 建议并预览 / 直接执行)、隐私与数据使用的总开关、通知频率的总开关。这些开关要放在设置里稳定的位置,不能每次换地方。
对照一下会发现:本章的三要素和这 18 条准则是同一个东西的两层抽象——三要素是架构,18 条准则是验收清单。
常见误区
- 万物皆对话框:把精确操作硬塞进自然语言,交互效率反而低于传统 GUI。先问「不用对话框怎么设计」
- 让模型直接吐 HTML/任意代码:安全和样式双重失控。生成式 UI 一定要走组件白名单 + schema 约束
- 假流式:完整结果拿到后再匀速播放,无法取消、破坏信任。要么真流式,要么老实转圈
- 长任务无状态:进度只存在内存里,刷新即丢失;失败只能重来。异步任务必须持久化状态机 + 断点恢复
- 只优化总耗时,不优化 TTFT:用户的耐心是在前 10 秒消耗掉的,首 token 延迟比总时长更值得投入
- 审批走过场:检查点只给「是/否」不给上下文,用户只能开盲盒,久而久之要么全批(检查点形同虚设)要么弃用
术语表
| 名词 | 定义 | 一句话直觉 | 常见混淆 |
|---|---|---|---|
| Generative UI | 模型输出结构化界面描述,前端据此渲染真实组件的交互模式 | 模型不当画师,当排版编辑 | ≠ 让模型直接生成 HTML/代码 |
| 组件白名单 | 生成式 UI 中允许模型调用的预定义组件集合 | 模型只能从菜单上点菜 | ≠ 对模型输出做关键词过滤 |
| TTFT | Time To First Token,从发请求到收到首个 token 的延迟 | 用户耐心开始倒计时的那一刻 | ≠ 总生成耗时;优化 TTFT ≠ 优化吞吐 |
| SSE | Server-Sent Events,服务器向浏览器单向推送的流式协议 | 一根只能往下流的水管 | ≠ WebSocket(双向);SSE 够用于绝大多数流式场景 |
| 感知性能 | 用户主观体验到的速度,与实际耗时可不一致 | 让人有事可看,比真的更快更重要 | ≠ 客观性能指标(QPS、延迟分位数) |
| 假流式 | 拿到完整结果后匀速逐字播放,伪装成实时生成 | 录播冒充直播 | ≠ 真流式;假流式无法中途取消 |
| 意图输入 | 用户向系统表达「想要什么」的环节,对话只是载体之一 | 说话、点击、拖拽都是在「输入意图」 | ≠ 提示词工程(那是表达技巧,不是交互层) |
| 检查点(Checkpoint) | 长任务中暂停等待人工审批的决策点 | 高速公路上的收费站 | ≠ 断点/快照(那是恢复机制,检查点是审批机制) |
| 断点恢复 | 任务失败后从上次进度继续而非重来的能力 | 游戏存档读档 | ≠ 重试(重试是同一个动作再来一遍,恢复是接着走) |
| 幂等 | 同一操作执行多次与执行一次效果相同 | 按十次电梯按钮和按一次一样 | ≠ 去重(去重是手段,幂等是性质) |
| 任务状态机 | 用显式状态和迁移规则建模异步任务生命周期 | 任务的「户口本」,只能在规定路线上迁户口 | ≠ 一堆布尔标志位(isRunning、isDone…) |
| 异步任务 vs 后台任务 | 前者是用户发起的长耗时任务,后者是系统自发的例行作业 | 异步任务有人等结果,后台任务没有 | 混为一谈会导致通知策略错配:后台任务不该打扰用户 |
参考材料
- Response Times: The 3 Important Limits — Nielsen 对响应时间三红线的权威重述,流式体验设计的理论根基
- Guidelines for Human-AI Interaction — Microsoft Research 的 18 条人机交互准则(CHI 2019),AI 界面设计的经典清单
- Microsoft HAX Toolkit — 上述准则的落地工具库,每条准则配有设计模式与示例
- Vercel AI SDK: Generative User Interfaces — 生成式 UI 的工业级参考实现
- OpenAI API: Streaming — SSE 流式响应的官方协议细节
- Building Effective Agents — Anthropic 的 Agent 工程总结,长任务与人工审批点的设计思路
小结
记住一个公式:AI Native 交互 = 意图输入 + 过程可见 + 结果可干预。对话框只是意图输入的一种载体,模糊意图用它,精确操作用 GUI,高频操作迟早固化成控件;生成式 UI 让模型的输出从文字升级成可操作的真实组件,白名单和降级矩阵是它的安全带;流式响应本质是感知性能工程,核心指标是首 token 延迟;长任务场景下,UI 要长成项目管理工具的样子——状态机、进度可读、检查点审批、分级通知、断点恢复。最后拿微软 18 条准则当验收清单过一遍。三要素齐了,用什么「框」反而不重要。