从 Markdown 到 Generative UI:AI 如何从“生成答案”进化到“生成界面”?

0 阅读29分钟

从 Markdown 到 Generative UI:AI 如何从“生成答案”进化到“生成界面”?

一、一个正在发生的变化:AI 不再只生成内容,也开始生成界面

在传统的 AI 聊天应用中,用户输入问题,大模型返回一段文本,前端负责把这段文本渲染出来。

例如:

帮我分析一下家庭月度预算。

模型可能会返回:

  • 房租:3000 元
  • 饮食:2000 元
  • 交通:500 元
  • 娱乐:800 元
  • 月收入:10000 元
  • 月结余:3700 元

前端使用 Markdown 渲染器,就能把这些内容展示得比较清晰。如果再增加 Markdown 表格、代码块、图表代码块等自定义渲染能力,用户甚至可以看到图表和一些可交互的组件。

但如果用户接着说:

把饮食预算调到 2500 元,把娱乐预算调到 500 元,看看月结余变化。再帮我找出哪些项目可以继续优化。

这时,传统聊天界面的局限就开始显现了。

用户需要反复输入修改要求,模型需要重新理解上下文、计算数据,再生成一段新的回答。即使前端嵌入了滑块和输入框,也可能需要重新请求模型才能更新相关内容。

能不能让 AI 直接生成一个预算面板?用户修改数字,结余、图表和建议立即同步更新;需要分析时,再由 AI 介入。

这就是 Generative UI 值得关注的原因。

它代表了一种不同的交互思路:AI 不仅可以生成用户要阅读的内容,还可以生成用户要操作的界面。

不过,要理解这项技术,我们需要先区分几个容易混淆的概念:Markdown 富内容、传统的动态组件渲染、Generative UI,以及更强调任务决策和持续协作的 Intelligent UI。

二、Markdown 富内容:给聊天消息增加展示能力

对于大多数使用 React 构建的 AI 应用,最常见的方案是使用 react-markdown。

整个流程很直接:

  1. 用户向模型发送问题。
  2. 模型返回 Markdown 文本。
  3. 前端接收模型的流式输出。
  4. Markdown 解析器把文本转换为对应的 React 元素。
  5. 浏览器把结果展示在聊天消息中。

例如,模型返回:

## 月度预算

| 项目 | 金额 |
|---|---:|
| 房租 | 3000 |
| 饮食 | 2000 |
| 交通 | 500 |

```chart
{
  "type": "pie",
  "data": [
    {"name": "房租", "value": 3000},
    {"name": "饮食", "value": 2000},
    {"name": "交通", "value": 500}
  ]
}

这里的 chart 代码块可以被前端拦截,再交给自己注册的图表组件处理。

示意代码如下:

import ReactMarkdown from "react-markdown";
import remarkGfm from "remark-gfm";

function MarkdownMessage({ content }) {
  return (
    <ReactMarkdown
      remarkPlugins={[remarkGfm]}
      components={{
        code({ className, children }) {
          const language =
            /language-(\w+)/.exec(className || "")?.[1];

          if (language === "chart") {
            return (
              <ChartRenderer
                source={String(children)}
              />
            );
          }

          return <code className={className}>{children}</code>;
        },
      }}
    >
      {content}
    </ReactMarkdown>
  );
}

这是一种简单而有效的扩展方式。通过自定义渲染器,我们可以将 Markdown 中的特定标记转换为图表、卡片、图片、表格甚至交互式表单。

2.1 这种方案的本质是什么?

本质上,模型输出的是一种内容描述,前端按照事先约定的规则解释这些内容。

例如:

  • ## 表示标题;
  • | 表示表格;
  • language-chart 表示图表数据;
  • language-form 表示表单配置;
  • 某种约定的标记表示按钮或卡片。

前端需要提前知道每种标记代表什么,以及如何渲染。

模型负责生成内容,前端负责决定如何呈现。

如果模型输出一个 chart 代码块,前端就渲染图表;如果没有输出,前端就不渲染。

这种方式完全可以做出非常复杂的应用。问题不在于它能不能实现交互,而在于随着交互逻辑越来越复杂,Markdown 是否还是合适的主要协议。

2.2 Markdown 富内容的边界在哪里?

假设预算面板需要以下功能:

  • 一个收入输入框;
  • 五个支出滑块;
  • 一个实时更新的结余数字;
  • 一张支出占比图;
  • 一张历史预算趋势图;
  • 一个优化建议面板;
  • 一个“生成优化方案”按钮。

如果全部通过 Markdown 和自定义代码块来描述,就必须为每种组件约定格式,还需要处理多个组件之间的状态共享。

例如,用户调整饮食预算后,支出占比图需要更新,月结余需要更新,预算建议也可能需要更新。

如果每次修改都重新请求模型,并重新生成整个 Markdown 消息,就会出现几个问题:

  1. 用户每次调整都产生一次模型请求,增加延迟和成本。
  2. 前端需要处理新旧消息之间的状态同步。
  3. 表单填写过程可能因为重新渲染而丢失。
  4. 图表、输入框和文字说明之间的关联逐渐变得复杂。
  5. 一旦引入多栏布局、复杂表单或跨组件联动,Markdown 就开始承担本来应该由应用状态和组件系统处理的工作。

但要注意:这些问题并不是 Markdown 天生无法解决的。开发者可以通过 React 状态管理、独立的组件实例、稳定的组件 ID 和局部更新来解决。

真正的问题是,如果我们已经需要一套独立的组件描述协议、状态管理机制和事件系统,那么继续把所有东西塞进 Markdown 代码块,就不一定是最合适的设计了。

三、什么是 Generative UI?

Generative UI,通常翻译为生成式 UI,指的是让 AI 系统能够根据任务动态提出或组合界面,而不是只能返回固定格式的文本内容。

传统 UI 的开发方式是:

开发者设计页面,编写组件,定义布局和交互,然后把用户数据填充到界面中。

Generative UI 则可以让模型根据用户需求,选择一组已经注册的组件,组织成新的界面结构,再交给前端渲染。

例如,用户说:

帮我做一个家庭预算计算器,可以调整收入和各项开支,并实时查看结余。

模型可以提出一个界面描述:

{
  "type": "Column",
  "children": [
    {
      "type": "Heading",
      "props": {
        "text": "家庭月度预算"
      }
    },
    {
      "type": "NumberInput",
      "props": {
        "label": "月收入",
        "value": 10000
      }
    },
    {
      "type": "Slider",
      "props": {
        "label": "饮食预算",
        "min": 0,
        "max": 5000,
        "value": 2000
      }
    },
    {
      "type": "Metric",
      "props": {
        "label": "月结余",
        "value": 3700
      }
    }
  ]
}

这份 JSON 不是 HTML,也不是 React 源码,而是一份声明式的界面描述。

它表达的是:

  • 需要什么组件;
  • 组件之间是什么层级关系;
  • 每个组件有哪些属性;
  • 初始数据是什么;
  • 在允许的情况下,组件应该绑定什么操作。

前端收到这份描述后,按照自己的组件注册表,将它转换成真正的 React 组件。

这就是 Generative UI 最核心的工程思路。

3.1 为什么使用 JSON,而不是让模型直接生成 JSX?

假设让模型直接生成下面的代码:

<div className="budget">
  <input type="range" />
  <button onClick={() => fetch("/api/pay")}>
    生成方案
  </button>
</div>

这看起来很灵活,但会带来严重的工程问题。

第一,模型生成的代码可能不完整,甚至无法编译。

第二,任意 JSX 或 JavaScript 可能包含危险的执行逻辑。

第三,模型可以引用应用中不存在的组件或函数。

第四,前端很难对任意生成代码的权限、网络访问和副作用进行统一管理。

因此,更可控的做法是让模型输出声明式数据,由应用决定哪些组件能够被创建,以及这些组件能够执行什么操作。

这类似于数据库查询和 SQL 执行之间的关系:描述请求和执行请求是两个不同的阶段。前者可以被校验,后者必须受到运行环境的约束。

当然,JSON 本身并不自动保证安全。即使模型只输出 JSON,恶意或错误的属性、危险 URL、越权操作等问题仍然需要在前端和后端进行校验。

3.2 Generative UI 的关键:组件注册表

前端可以维护一个组件注册表:

const componentRegistry = {
  Column,
  Row,
  Card,
  Heading,
  Text,
  NumberInput,
  Slider,
  Button,
  Chart,
  Table,
  Metric,
};

然后编写一个递归渲染器:

function renderNode(node, context) {
  const Component = componentRegistry[node.type];

  if (!Component) {
    return null;
  }

  const children = node.children?.map((child) =>
    renderNode(child, context)
  );

  return (
    <Component
      {...validateProps(node.type, node.props)}
      context={context}
    >
      {children}
    </Component>
  );
}

这里的 validateProps 代表需要由开发者实现的属性校验逻辑,并不是一个自动存在的 API。

实际工程还需要处理:

  • 输入数据的结构验证;
  • 组件属性的类型和范围校验;
  • 事件绑定;
  • 错误降级;
  • 流式输出中的不完整数据;
  • 组件实例的稳定标识;
  • 状态和生命周期管理。

整个过程可以概括为:

模型生成 UI 描述 → 校验描述 → 查找组件 → 创建组件树 → 浏览器渲染。

模型决定的是受约束的组合方式,而不是获得执行任意前端代码的权限。

相关实现可以参考 assistant-ui 的 Generative UI 文档,其中介绍了通过 present 工具输出 JSON 组件树,并由组件库进行解析和渲染。

四、Generative UI 到底怎样渲染?是不是页面里面又嵌套了一个页面?

这是一个非常重要的问题。

答案是:通常不是。

如果前端使用 React 实现,生成式 UI 可以直接作为现有 React 应用中的一个子树。

例如:

function AssistantMessage({ message }) {
  return (
    <article className="assistant-message">
      <MarkdownRenderer content={message.text} />

      {message.uiSpec && (
        <GenerativeUIRenderer
          spec={message.uiSpec}
        />
      )}
    </article>
  );
}

GenerativeUIRenderer 接收 UI 描述,生成普通的 React 元素。

假设模型生成了:

{
  "type": "Card",
  "children": [
    {
      "type": "Heading",
      "props": {
        "text": "预算分析"
      }
    },
    {
      "type": "Chart",
      "props": {
        "chartType": "pie",
        "data": [
          {"name": "房租", "value": 3000},
          {"name": "饮食", "value": 2000}
        ]
      }
    }
  ]
}

前端会把它解析为类似这样的 React 结构:

<Card>
  <Heading text="预算分析" />
  <Chart
    chartType="pie"
    data={data}
  />
</Card>

这段 JSX 仅用于说明最终组件树的形态,并不是要求模型返回或执行 JSX。

最终页面中的 DOM 仍然由 React 正常管理。样式、事件处理、响应式布局、组件状态和无障碍属性,都可以使用现有的前端工程体系实现。

4.1 三种常见的展示方式

方式一:嵌入聊天消息

聊天页面
└── AI 消息
    ├── 文本说明
    └── 预算计算器
        ├── 收入输入框
        ├── 支出滑块
        └── 结余图表

适合短小、临时、与当前问题紧密相关的工具。

方式二:在独立工作区展示

应用页面
├── 对话区
│   └── AI 的解释和操作建议
└── 工作区
    └── 预算计算器
        ├── 参数面板
        ├── 图表区
        └── 优化建议

适合复杂的任务、较长的交互过程和需要持续保留状态的工作流。

方式三:嵌入独立应用

通过 iframe 等机制嵌入单独部署的应用,或者通过受控的远程 UI 协议展示来自其他服务的界面。

这种方式更适合已有独立 Web 应用、第三方工具或需要隔离执行环境的场景,但需要额外考虑跨域通信、权限控制和生命周期管理。

因此,Generative UI 不要求采用某一种特定的页面结构。它关注的是界面能否被动态描述和组合,以及运行时如何安全地将其呈现出来。

五、从 Generative UI 到 Intelligent UI:区别究竟在哪里?

如果 Generative UI 解决的是“如何生成和渲染界面”,那么更完整的 Intelligent UI 体验还要回答另外两个问题:

  1. 什么情况下应该生成界面?
  2. 生成的界面如何与任务状态、用户操作和 Agent 协作?

这两个问题会把前端渲染问题扩展成一个完整的应用架构问题。

5.1 模型决定什么时候需要 UI

并不是所有问题都适合生成界面。

用户问:

JavaScript 中的 Promise 是什么?

直接回答通常更好。

用户问:

帮我对比三种缓存策略,调整缓存命中率和请求量,比较不同场景下的性能。

这时,交互式表格、图表或模拟器可能更合适。

因此,一个更完整的系统可以让模型在文本回答、结构化数据、图表、表单和交互式工具之间选择。

这种选择可以通过多种方式实现:

  • 在模型提示中说明可用的呈现方式;
  • 将 present_ui 注册成模型可调用的工具;
  • 让 Agent 根据任务类型选择预定义的 UI 模板;
  • 由后端工作流决定何时向前端发送 UI 描述;
  • 使用混合策略,允许模型提出方案,但由应用的规则决定是否接受。

注意,真正的决策权不一定完全属于模型。对于高风险操作或固定业务流程,系统可能要求使用确定性的规则,而不是让模型自由选择。

5.2 UI 不再只是回答,而是任务的操作界面

以预算工具为例。

用户调整饮食预算后,系统可以分为两种更新路径。

第一种是纯前端更新:

用户拖动滑块
    ↓
React 状态更新
    ↓
重新计算预算
    ↓
图表和结余立即更新

第二种是需要 AI 参与的更新:

用户点击“分析这次调整”
    ↓
前端发送预算状态和用户意图
    ↓
Agent 分析预算变化
    ↓
必要时调用业务工具
    ↓
返回建议或新的 UI 描述
    ↓
前端更新对应区域

这两种路径可以同时存在。

一个重要的设计原则是:不需要模型参与的操作,不要强制每次都调用模型。

调整滑块、修改输入框和重新计算加减法,都可以由前端即时完成。

而当用户要求解释变化、寻找节省空间、生成方案或者查询外部数据时,再调用模型和业务工具。

这样才能兼顾交互速度、成本和智能程度。

5.3 组件之间共享状态,才是真正的工程难点之一

生成一个包含三个组件的界面,并不等于实现了完整的交互体验。

假设预算面板中包含:

  • 收入输入框;
  • 支出滑块;
  • 结余数字;
  • 支出占比图;
  • AI 优化建议。

如果这些组件各自维护独立状态,就很容易出现数据不一致。

例如,滑块已经从 2000 元调整到 2500 元,但图表仍然显示旧数据。

解决方式通常不是让模型重新生成整个页面,而是由应用维护一份统一的任务状态。

type BudgetState = {
  income: number;
  rent: number;
  food: number;
  transport: number;
  entertainment: number;
};

function calculateBalance(state: BudgetState) {
  return (
    state.income -
    state.rent -
    state.food -
    state.transport -
    state.entertainment
  );
}

多个组件订阅同一份状态:

BudgetState
├── IncomeInput
├── FoodSlider
├── BalanceMetric
├── ExpenseChart
└── BudgetSummary

当 food 发生变化时,依赖它的组件可以自动更新。

在 React 中,可以用 useState、useReducer、Context 或其他状态管理工具实现。

如果 UI 描述需要跨消息、跨页面甚至跨会话保留,还需要将状态持久化,并设计状态恢复和版本兼容机制。

要注意,组件树和业务状态是两个不同的东西。模型可以重新生成组件树,但这不意味着用户已经输入的数据就应该被覆盖。

六、真正的交互闭环:UI 事件怎样回到 Agent?

这是从“能展示组件”走向“能够完成任务”的关键。

在传统页面中,按钮点击通常会触发前端回调:

<Button onClick={handleClick}>
  提交
</Button>

而在 Generative UI 中,按钮可能是模型动态组合出来的。

但动态生成并不意味着事件处理逻辑也要由模型随意生成。

更安全的方式是让模型声明一个已注册的动作。

例如:

{
  "type": "Button",
  "props": {
    "label": "分析预算"
  },
  "action": {
    "type": "analyze_budget"
  }
}

前端维护动作注册表:

const actionRegistry = {
  analyze_budget: async (context) => {
    return sendAgentEvent({
      event: "analyze_budget",
      payload: context.budget,
    });
  },
};

当用户点击按钮时:

  1. 前端找到对应的动作处理器;
  2. 收集经过校验的业务状态;
  3. 将事件和必要的数据发送给 Agent;
  4. Agent 根据事件决定是否继续推理、调用工具或更新界面;
  5. 前端接收新结果,并更新相应区域。

完整流程如下:

模型生成 UI 描述
        ↓
前端验证并渲染组件树
        ↓
用户操作组件
        ↓
前端产生结构化事件
        ↓
Agent 接收事件和任务状态
        ↓
模型推理 / 业务工具执行
        ↓
返回数据、动作结果或新 UI 描述
        ↓
前端局部更新或替换组件树

这是一种双向交互架构。

不过,UI 事件不一定每次都要回到大模型。纯前端的即时交互可以在本地完成;需要业务操作时可以调用后端;只有需要智能决策时才需要进入 Agent。

这也解释了为什么一个成熟的生成式 UI 系统通常需要事件协议、动作注册表、任务状态管理和工具执行机制,而不只是一个 JSON 渲染器。

七、流式渲染:为什么不能等模型全部生成完再显示?

传统聊天应用通常已经支持 token 流式输出。

例如:

正在生成文字……
正在生成表格……
正在生成图表数据……
生成完成

但如果 UI 是通过一份结构化描述生成的,前端还需要解决一个问题:结构尚未完整时,能不能开始渲染?

假设模型正在输出:

{
  "type": "Card",
  "children": [
    {
      "type": "Heading",
      "props": {
        "text": "预算分析"
      }
    },
    {
      "type": "Chart",
      "props": {
        "data": [

这时 JSON 还不完整,直接执行 JSON.parse 就会失败。

因此,工程实现通常有几种选择。

方案一:完整解析后渲染。

等待模型输出完整结构,再验证并创建组件树。

优点是简单可靠;缺点是大型界面需要等待更久。

方案二:流式结构化解析。

采用支持增量解析的协议或解析器,当某个节点已经完整时,就先渲染该节点。

例如:

收到 Card 节点
    ↓
先展示卡片容器

收到 Heading 节点
    ↓
展示标题

收到 Chart 节点
    ↓
展示图表占位符

收到完整数据
    ↓
渲染图表

方案三:分阶段生成界面。

模型先返回一个初始界面,再逐步补充数据、组件和动作。

例如先生成一个预算面板骨架,再填入计算结果和建议。

这种方法可以避免让前端直接解析任意不完整的 JSON 字符串。

实际工程中,还要处理节点 ID、重复事件、更新顺序、组件卸载、网络中断和不完整数据等问题。

因此,流式 UI 并不是简单地把文本 token 直接塞进 JSON.parse,而是需要一套明确的增量更新协议和运行时机制。

八、安全问题:为什么组件白名单仍然不够?

很多文章会把 Generative UI 的安全优势总结为一句话:

使用 JSON 和组件白名单,就不会有 XSS 风险。

这种说法并不准确。

组件白名单可以限制模型创建哪些类型的组件,但不能自动保证组件属性、业务操作和数据内容都是安全的。

例如:

{
  "type": "Button",
  "props": {
    "label": "删除全部订单"
  },
  "action": {
    "type": "delete_all_orders"
  }
}

即使 Button 是合法组件,delete_all_orders 也不应该因为它出现在模型输出中,就自动获得执行权限。

一个完整的安全设计至少应该包括以下几个方面。

8.1 组件白名单

模型只能选择开发者注册的组件。

const registry = {
  Card,
  Text,
  Table,
  Chart,
  Input,
  Button,
};

未知组件必须拒绝渲染或安全降级。

8.2 属性验证

对模型输出进行结构和类型校验。

例如:

  • 滑块的 min 不能大于 max;
  • 图表数据必须符合规定的结构;
  • 文本长度需要受到限制;
  • URL 必须经过协议和域名校验;
  • 组件嵌套层级和总数量需要设置上限。

8.3 动作白名单

模型只能引用已注册的动作类型。

前端负责分发事件,后端负责重新验证权限、业务参数和用户身份。

8.4 高风险操作需要授权

删除数据、付款、提交订单等操作,不能只凭模型生成了一个按钮就直接执行。

应当结合用户确认、服务端权限校验、幂等控制以及必要的撤销机制。

8.5 避免任意代码执行

不要对模型返回的字符串使用 eval、new Function 或其他方式执行任意 JavaScript。

如果需要富文本或 HTML,必须单独考虑可信内容和消毒策略。

总之,声明式 UI 的安全优势在于缩小了可执行能力的范围,而不是让安全问题自动消失。

九、工程上如何把 ReactMarkdown 方案升级成 Generative UI?

对于已经有 ReactMarkdown、自定义代码块和流式消息处理的项目,不需要推倒重来。

更合理的方案是逐步引入结构化 UI。

第一步:保留 Markdown,增加独立的 UI 消息类型

不要急着把所有内容改成 JSON。

可以让一条 AI 消息同时包含文本和 UI:

type AssistantPart =
  | {
      type: "text";
      content: string;
    }
  | {
      type: "ui";
      spec: UISpec;
    };

例如:

const message = [
  {
    type: "text",
    content: "下面是你的预算计算器,可以直接调整支出。",
  },
  {
    type: "ui",
    spec: budgetUISpec,
  },
];

这样,普通问答仍然使用 Markdown;需要交互界面时,再使用结构化 UI。

这种设计也比在 Markdown 文本中混入大量自定义标记更容易维护。

第二步:定义一份有限的 UI 描述协议

例如:

type UISpec = {
  id: string;
  type: string;
  props?: Record<string, unknown>;
  children?: UISpec[];
};

实际项目应进一步使用 Zod、JSON Schema 或其他运行时校验工具,定义每一种组件允许的属性。

不要让 Record<string, unknown> 成为最终的安全边界。

第三步:实现组件注册表和递归渲染器

将 UI 描述转换为 React 元素。

渲染器需要负责:

  • 查找组件;
  • 校验属性;
  • 创建子节点;
  • 处理稳定 ID;
  • 分发动作;
  • 处理错误和未知节点。

组件样式仍然由前端开发者控制。

第四步:增加状态管理

不要把用户每一次输入都转成新的模型请求。

例如预算计算器可以维护统一的 BudgetState,所有表单、图表和指标都依赖同一份状态。

对于纯计算逻辑,直接在前端执行。

第五步:增加事件协议

为需要进入 Agent 的交互定义统一事件格式:

type UIEvent = {
  event: string;
  taskId: string;
  componentId: string;
  payload: unknown;
};

例如:

{
  "event": "analyze_budget",
  "taskId": "budget-task-01",
  "componentId": "budget-panel",
  "payload": {
    "income": 10000,
    "rent": 3000,
    "food": 2500,
    "transport": 500,
    "entertainment": 500
  }
}

这里的 ID 只是示例,真实项目需要正确生成和管理任务标识。

事件传到后端后,应当由 Agent 或业务服务进行校验和处理,再返回结果。

第六步:支持局部更新和流式更新

模型返回新的 UI 描述时,不一定要重新创建整个界面。

可以根据稳定的节点 ID 更新特定节点,也可以将模型输出设计成增量操作,例如新增节点、修改属性和替换数据。

但增量更新协议必须定义清楚,不能简单地对两个 JSON 对象做浅层合并,就假设状态和生命周期一定正确。

第七步:让模型决定何时使用 UI

最后再将 UI 生成能力接入模型的决策过程。

可以提供一个 present_ui 工具,或者让 Agent 输出经过约束的 UI 数据。

模型可以根据任务选择:

  • 直接回答;
  • 返回表格;
  • 创建图表;
  • 生成交互式表单;
  • 创建一个完整的任务面板。

需要强调的是,这里的“自主决定”是在应用所提供的组件、权限和策略范围内做选择,并不是允许模型无限制地创建和执行任何界面代码。

十、一个完整的例子:家庭预算 Intelligent UI

下面把前面的概念串起来,看看实际应用应该如何工作。

用户输入:

帮我规划家庭月度预算,能够调整每项开支,查看结余,并在需要时给出优化建议。

10.1 意图识别

Agent 判断这是一个适合交互式 UI 的任务,因为用户需要反复调整参数,并观察多个指标的变化。

系统选择预算面板模板或提出一份新的 UI 描述。

10.2 创建初始界面

前端渲染:

家庭月度预算
────────────────────────
月收入       [ 10000 ]

房租         [ 3000 ]
饮食         [ 2000 ]
交通         [  500 ]
娱乐         [  800 ]

月支出        6300
月结余        3700

[支出占比图表]

[分析预算] [生成优化方案]

这个界面是 React 组件树,不是一张截图,也不必是嵌入的独立网页。

10.3 用户调整开支

用户将饮食预算从 2000 元调整到 2500 元。

前端更新状态:

const nextBudget = {
  ...budget,
  food: 2500,
};

假设收入为 10000 元,其他支出不变,则月支出从 6300 元增加到 6800 元,月结余从 3700 元减少到 3200 元。

这一步由前端计算即可,不需要请求大模型。

图表、结余指标和表单会根据统一状态同步更新。

10.4 用户请求 AI 分析

用户点击“分析预算”。

前端发送当前预算状态和事件,Agent 可以进一步:

  1. 验证数据;
  2. 计算各类支出占比;
  3. 查询用户授权的数据源(如果需要);
  4. 生成节省建议;
  5. 返回文字解释,或者更新建议面板。

例如,Agent 返回:

饮食预算增加了 500 元,月结余相应减少 500 元。如果希望保持 3500 元的月结余,可以考虑将娱乐预算调整到 200 元,或者在其他支出中寻找节省空间。

这只是一个示例建议,不代表实际用户一定应该采用该方案。

10.5 用户进一步调整方案

用户说:

帮我比较两种方案,一种保持饮食预算,另一种减少娱乐和交通开支。

Agent 可以生成一个对比面板,显示两种方案的支出结构、月结余和各项差异。

前端更新对比区域,保留用户原先填写的预算数据。

此时,界面不只是 AI 回答的附属内容,而是用户与 AI 共同操作和修改的任务载体。

这才是从静态内容展示到交互式 AI 应用的完整演进过程。

十一、Generative UI 与 Intelligent UI 的关系

为了避免术语混乱,可以用下面这张表概括。

维度Markdown 富内容Generative UIIntelligent UI 体验
输出形式文本和约定标记结构化界面描述或工具驱动的 UI根据任务选择合适的表达方式
界面组合以预定义渲染规则为主模型可以组合已注册组件组合能力与任务决策相结合
交互能力可以实现可以实现强调与任务流程的持续协作
状态管理由应用实现由应用实现通常需要跨组件、跨步骤协调
Agent 闭环可有可无可有可无,也可以接入通常是核心体验之一
流式更新文本 token 流式输出较常见可以支持结构化增量渲染可以根据任务状态持续更新
安全机制Markdown 解析和组件规则组件白名单、结构校验、动作权限还需要任务权限、工具授权和状态控制

这张表表达的是常见设计倾向,不是绝对边界。

Generative UI 并不必然支持状态共享、流式更新或 Agent 闭环;这些能力需要额外的协议和运行时实现。Intelligent UI 也不必然使用 Generative UI,它同样可以通过预定义界面、确定性业务逻辑和传统工具调用来实现。

十二、为什么值得做这项技术?

从工程角度来看,Generative UI 和 Intelligent UI 的价值并不在于“让 AI 能生成更多组件”,而在于改善用户完成任务的方式。

12.1 降低交互成本

传统对话要求用户通过语言不断描述修改。

交互式界面则允许用户直接调整数字、筛选条件、时间范围和选项。

对于需要反复试验的任务,直接操作往往比反复描述更高效。

12.2 将自然语言和精确操作结合起来

自然语言擅长表达目标,例如:

帮我降低支出,但尽量不要影响生活质量。

界面擅长表达精确参数,例如:

将饮食预算设为 2500 元,娱乐预算设为 500 元。

两者结合,可以同时利用 AI 的理解能力和传统 UI 的确定性。

12.3 减少为每一种需求单独开发页面的成本

传统应用往往需要为每个业务场景提前设计完整页面。

生成式 UI 可以让模型在一个受控的组件库中组合界面,从而覆盖更多长尾任务。

但它不能消除前端开发成本。组件、设计系统、权限、业务接口、状态管理和错误处理仍然需要开发者建设。

12.4 让 AI 的结果更容易被检查和修改

一段文字建议不一定容易直接执行。

结构化表格、参数面板和对比图则可以让用户直接检查数据、修改输入和观察结果。

不过,界面可视化并不等于结果正确。重要的业务计算和数据校验仍应由可信的程序逻辑完成。

12.5 为 Agent 提供更自然的操作入口

Agent 不再只需要等待用户在聊天框中输入下一句话。

用户可以通过界面发起操作、确认参数、选择候选方案和授权业务动作。

这让 Agent 更容易融入真实的工作流。

十三、工程实践中的几个误区

最后,有几个值得特别强调的结论。

误区一:Generative UI 就是让模型输出 JSON。

不是。JSON 只是常见的序列化形式。真正重要的是组件描述协议、验证规则、渲染器和运行时。

误区二:Generative UI 一定比 Markdown 更高级。

不一定。简单问答、文档说明和代码解释通常使用 Markdown 更合适。只有当任务确实需要动态组合界面时,结构化 UI 才更有优势。

误区三:Intelligent UI 必须每次操作都请求模型。

恰恰相反。能够把本地交互、业务计算和智能推理分开,才是良好的架构设计。

误区四:组件白名单就能解决所有安全问题。

组件白名单只是第一层防护。属性校验、动作权限、后端授权、数据校验和高风险操作确认仍然必不可少。

误区五:生成式 UI 就是把另一个网页嵌套进当前页面。

不一定。最常见的方式是在现有应用中动态创建组件树。iframe 只是多种展示与隔离方式中的一种。

误区六:Intelligent UI 是 Generative UI 的另一个名字。

两者有关联,但不是严格的同义词。Generative UI 更容易用来描述动态界面的生成和渲染机制;Intelligent UI 更适合描述 AI 根据任务选择和调整交互形式的产品体验。涉及具体厂商时,还需要以其公开定义和技术文档为准。

十四、总结:从聊天消息走向任务界面

回到最初的问题:如果已经有 ReactMarkdown 和自定义代码块渲染,还有必要做 Generative UI 吗?

答案取决于产品需要。

如果你的应用主要是问答、解释、代码生成和内容总结,Markdown 富内容完全可以满足需求。

如果你希望模型动态组合表格、表单、图表和布局,可以引入声明式 UI 协议与组件注册表。

如果你希望用户通过这些界面持续调整参数、触发业务工具、查看执行结果,并让 Agent 在整个任务过程中保持上下文,就需要进一步设计状态管理、事件协议、工具调用和增量更新机制。

这不是一次简单的渲染器升级,而是从内容展示架构向任务交互架构的演进。

Markdown 解决的是如何呈现内容,Generative UI 解决的是如何动态组合界面,而 Intelligent UI 所强调的,是如何根据任务选择合适的界面,并让界面成为 AI 与用户共同完成任务的入口。

对于技术团队来说,最务实的路线不是抛弃 Markdown、追求完全动态的页面,而是采用混合架构:文本交给 Markdown,动态界面交给结构化组件协议,确定性计算交给前端或业务服务,复杂决策交给 Agent,再通过受控的事件通道将它们连接起来。

当这些部分协同工作时,AI 应用才有机会从一个能够生成答案的聊天框,演进为一个能够帮助用户完成工作的交互式应用。