芥末
发布于 2026-04-17 / 0 阅读
0
0

Agent 对话中卡片式 UI 的协议设计

大语言模型(Large Language Model,LLM)的回答通常是一段连续的文本流,前端把这些 token 拼起来,再按 Markdown 渲染成标题、列表、链接、代码块等内容。

但 Agent(智能体)时代的对话界面不再只有文字。用户问“推荐一款适合跑步的手表”,理想的回答可能是一组商品卡片;用户问“明天去上海的高铁”,回答里可能需要车次卡片;用户问“帮我订一张机票”,回答里甚至需要表单、按钮和支付入口。

这时问题就变成了:如何让模型输出的文本流里自然地出现可交互 UI(User Interface,用户界面)组件,并且保证数据准确、跨端一致、事件可控?

卡片式对话系统至少要解决三件事:

  1. 卡片怎么嵌入 Markdown 流:模型输出的是文本,卡片是组件,两者需要一套兼容流式解析的标记方式。
  2. 卡片数据从哪里来:价格、库存、航班、用户权益等实时数据不能让模型随便生成。
  3. 多团队怎么协作:如果每个业务方都发明一套格式,前端、客户端和后端会很快陷入协议碎片化。

整体结构可以抽象成这样:

flowchart LR
    U[用户] --> A[Agent 编排层]
    A --> M[LLM 文本生成]
    A --> T[Tool 工具层]
    M --> P[消息传输协议]
    T --> P
    P --> R[Markdown / UI 渲染器]
    R --> C[卡片组件]
    C --> E[事件通信协议]
    E --> A
    E --> API[业务 API]

其中,LLM 负责理解意图和组织回答,Tool 负责拿真实业务数据,协议层负责把文本、结构化 UI 和交互事件统一起来。


一、卡片如何嵌入 Markdown 流

Markdown 的能力边界很清楚:它能表达标题、段落、列表、链接、图片、代码块,但不能直接表达“这里要渲染一个商品卡片”。

所以需要在 Markdown 上加一层扩展语义。这个扩展不能破坏原有 Markdown 解析,也要适合 token 流式输出,否则前端会在解析过程中看到半截标记、半截 JSON(JavaScript Object Notation,JavaScript 对象表示法),体验非常差。

常见方案有三类:代码块扩展、占位符替换、自定义标签。


1. 代码块扩展:借用 Markdown 的 language 字段

Markdown 代码块本来就支持 language 标识,例如:

```javascript
console.log("hello");
```

渲染器通常会把 javascript 当作语法高亮语言。卡片式对话可以借用这个位置,把 language 当成组件类型。

例如模型输出:

为你推荐这款商品:

```ProductCard
{
  "title": "iPhone 15 Pro Max",
  "description": "钛金属设计,A17 Pro 芯片",
  "itemPrice": 9999,
  "imageUrl": "https://example.com/iphone.jpg",
  "discount": "限时优惠"
}
```

支持分期免息,点击卡片可以查看详情。

前端解析 Markdown 时,如果发现代码块的 language 是 ProductCard,就不按普通代码渲染,而是把代码块内容解析成 JSON,传给对应组件。

React 中可以这样扩展 react-markdown

import Markdown from "react-markdown";
import { ProductCard } from "./components/ProductCard";
import { FlightCard } from "./components/FlightCard";

const CARD_COMPONENTS = {
  ProductCard,
  FlightCard,
};

function fallbackCodeBlock(className: string | undefined, children: React.ReactNode) {
  return (
    <pre>
      <code className={className}>{children}</code>
    </pre>
  );
}

export function ChatMessage({ markdown }: { markdown: string }) {
  return (
    <Markdown
      components={{
        code(props) {
          const { children, className } = props;
          const match = /language-(\w+)/.exec(className || "");
          const componentName = match?.[1];

          if (!componentName) {
            return fallbackCodeBlock(className, children);
          }

          const CardComponent =
            CARD_COMPONENTS[componentName as keyof typeof CARD_COMPONENTS];

          if (!CardComponent) {
            return fallbackCodeBlock(className, children);
          }

          try {
            const cardProps = JSON.parse(String(children));
            return <CardComponent {...cardProps} />;
          } catch {
            return fallbackCodeBlock(className, children);
          }
        },
      }}
    >
      {markdown}
    </Markdown>
  );
}

代码块扩展适合生产环境,原因主要有三个。

特性说明
兼容现有 Markdown 解析器大多数 Markdown 解析器都支持代码块和 language 字段,不需要从零写 parser
JSON 不会被二次解析代码块内部不会被 Markdown 当作列表、引用、链接处理,结构化数据更安全
适合流式输出遇到开始标记后可以持续缓冲,等结束标记出现再解析 JSON

流式渲染时,前端不应该在代码块尚未闭合时就解析 JSON。更合理的流程是:

flowchart LR
    A[收到 ```ProductCard] --> B[进入代码块缓冲状态]
    B --> C[持续接收 token]
    C --> D{是否收到结束标记}
    D -- 否 --> B
    D -- 是 --> E[解析 JSON]
    E --> F{解析成功}
    F -- 是 --> G[渲染卡片]
    F -- 否 --> H[降级为普通代码块或文本]

为了让 LLM 稳定输出这种结构,系统提示词里需要写清楚格式约束:

当需要展示结构化信息时,使用 Markdown 代码块描述卡片。

规则:
- 代码块 language 使用组件名,采用 PascalCase,例如 ProductCard、FlightCard
- 代码块内容必须是合法 JSON
- JSON 属性名使用 camelCase
- 字符串必须使用双引号
- 不要在 JSON 中写注释

示例:

```ProductCard
{
  "title": "商品名称",
  "itemPrice": 99,
  "imageUrl": "https://example.com/img.jpg"
}
```

工程上还要做两层保护:

  1. 组件白名单:只允许渲染注册过的组件,不能让模型任意指定组件名。
  2. Schema 校验:每种卡片都有对应 JSON Schema,字段缺失或类型错误时降级展示。

2. 占位符替换:轻量,但流式体验容易出问题

占位符方案让模型只输出一个特殊标记,前端识别后替换为卡片。

例如:

推荐上午 10 点从杭州出发前往上海的高铁 [(TrainCard:G1234)],早班直达。

这种方式的好处是模型不需要生成完整 JSON,只要输出组件名和业务 ID 即可。真实数据可以由前端或服务端根据 G1234 再去查询。

但它在流式场景中有一个天然问题:模型是逐 token 输出的,当页面只收到 [(Train 时,前端无法判断这到底是普通文本,还是一个尚未完成的占位符。如果直接展示,用户会看到一串临时标记;如果一直等待,又会影响普通文本输出。

可以用缓冲状态机缓解这个问题:

flowchart LR
    A[收到字符] --> B{是否遇到 '[('}
    B -- 否 --> C[按普通文本输出]
    B -- 是 --> D[进入占位符缓冲]
    D --> E{是否遇到 ')]'}
    E -- 是 --> F[生成组件占位节点]
    E -- 否 --> G{是否超过缓冲上限}
    G -- 否 --> D
    G -- 是 --> H[回退为普通文本]

占位符适合这类场景:

  • 卡片数据完全由服务端或前端查询;
  • 模型只需要决定“这里要展示什么类型的卡片”;
  • 卡片渲染前允许出现骨架屏。

不适合这类场景:

  • 卡片需要携带复杂结构化数据;
  • 希望模型输出和 UI 一次成型;
  • 对流式展示稳定性要求很高。

3. 自定义标签:表达力强,但解析成本高

自定义标签使用类似 XML(Extensible Markup Language,可扩展标记语言)或 HTML(HyperText Markup Language,超文本标记语言)的结构描述 UI 或动作。

例如:

<artifact title="创建项目" id="artifact_1">
  <action type="shell">npm install</action>
  <action type="shell">npm run dev</action>
</artifact>

它的优势是表达能力强:天然支持属性、嵌套结构、多 Action 组合,也可以描述更复杂的交互语义。

但代价也很明确:

  • 需要维护独立的流式 XML parser;
  • 要处理 Markdown parser 和 XML parser 的边界;
  • 标签没闭合、属性转义、嵌套错误都要兜底;
  • 安全策略更复杂,不能把标签内容当作可执行代码。

自定义标签更适合这些情况:

  • 模型提供方没有 Tool Calling(工具调用)能力,只能靠文本模拟结构化动作;
  • 团队已有成熟 XML-like 解析基础设施;
  • 需要跨多家模型统一描述复杂 UI 或任务动作。

4. 三种嵌入方式怎么选

方案核心思路优点代价适合场景
代码块扩展使用代码块 language 表示组件类型,代码体放 JSON复用 Markdown 解析链路,流式友好,易排查需要约束模型输出合法 JSON大多数卡片式对话场景
占位符替换在文本中放特殊标记,前端替换为组件改造轻,模型输出简单流式时可能出现半截标记,数据不随标记携带服务端异步补数据、骨架屏可接受
自定义标签用 XML-like 标签描述组件和动作表达力强,支持复杂嵌套解析器成本高,边界处理复杂多模型统一管控、复杂 Action 描述

如果没有特殊历史包袱,代码块扩展通常是最稳的默认选择。它没有引入新的语法体系,又能把组件类型和结构化数据都放进 Markdown 流里。


二、卡片数据从哪里来

卡片有两部分内容:

  • UI 结构:这是商品卡、航班卡、用户卡,包含标题、图片、按钮等展示区域。
  • 业务数据:商品价格、库存、航班余票、用户优惠、推荐理由等实时信息。

这两者必须解耦。LLM 可以判断“这里适合展示商品卡”,但不应该负责生成实时价格和库存。模型可能根据训练语料编出一个看似合理的价格,也可能生成不存在的商品链接,这些内容在真实业务里不可接受。

数据获取方案通常会经历三个阶段:模型直出、增量 Patch、Tool 驱动。


1. 模型直出:适合演示,不适合实时业务

最简单的方式是让 LLM 直接输出完整卡片:

```ProductCard
{
  "title": "智能手环",
  "price": 299,
  "stock": 20,
  "imageUrl": "https://example.com/band.jpg"
}
```

这种方式适合静态内容,例如百科卡片、功能介绍、固定流程说明。只要数据不要求实时准确,模型直出可以快速完成验证。

但它不适合这些数据:

数据类型模型直出的风险
商品价格价格会变化,模型可能生成过期价格
库存状态库存实时变化,模型无法感知
优惠权益不同用户权益不同,模型无法判断
商品链接容易生成不存在或错误链接
推荐结果真实推荐通常来自召回、排序、过滤链路

所以模型直出的边界很清楚:只能用于低风险、非实时、可容忍误差的内容


2. 增量 Patch:模型先占位,服务端后补数据

更可靠的方式是让模型只输出卡片骨架,真实数据由服务端异步查询。

模型输出:

为你推荐这款商品:

```ProductCard
{
  "id": "candidate_001"
}
```

这款商品适合日常运动使用。

前端看到 ProductCard 但字段不完整时,先渲染骨架屏。服务端同时根据 candidate_001 发起 RPC(Remote Procedure Call,远程过程调用)或业务 API(Application Programming Interface,应用程序编程接口)查询,拿到真实数据后,通过 Patch 更新前端已有消息。

时序如下:

sequenceDiagram
    participant C as 前端
    participant A as Agent 服务
    participant M as LLM
    participant B as 业务服务

    C->>A: 用户问题
    A->>M: 请求生成回答
    M-->>A: 流式返回 Markdown + ProductCard 占位
    A-->>C: 推送 full/append 消息
    C-->>C: 渲染商品卡骨架屏
    A->>B: 根据 id 查询真实商品数据
    B-->>A: 返回标题、价格、图片、库存
    A-->>C: 推送 patch 消息
    C-->>C: 替换占位 JSON,渲染完整卡片

Patch 消息可以设计成这样:

[
  {
    "type": "full",
    "data": {
      "messageId": "msg_001",
      "markdown": "为你推荐这款商品:\n```ProductCard\n{\n  \"id\": \"candidate_001\"\n}\n```\n"
    }
  },
  {
    "type": "patch",
    "messageId": "msg_001",
    "patch": [
      {
        "op": "replace-substring",
        "path": "/markdown",
        "substring": "```ProductCard\n{\n  \"id\": \"candidate_001\"\n}\n```",
        "replacement": "```ProductCard\n{\n  \"id\": \"12345\",\n  \"title\": \"智能手环\",\n  \"price\": 299,\n  \"imageUrl\": \"https://example.com/product.jpg\",\n  \"description\": \"支持健康监测和运动追踪\"\n}\n```"
      }
    ]
  }
]

这个方案解决了数据准确性问题,但会带来两个工程负担。

第一,体验上会出现跳变。 用户先看到骨架屏,等业务数据返回后才看到真实卡片。如果后端调用链较长,等待感会很明显。

第二,字符串替换不够稳。 replace-substring 依赖精确匹配占位片段。模型多输出一个空格、换行格式不同、字段顺序变化,都可能导致替换失败。

生产环境里可以做几层增强:

问题处理方式
占位片段难匹配给每个卡片生成稳定 cardId,Patch 直接按节点 ID 更新
Patch 乱序消息带 seq 序号,前端按序应用
数据返回慢骨架屏加超时态,超过阈值显示“暂时无法获取”
Patch 失败降级为文本或重新拉取完整消息
字段不可信Patch 后仍按卡片 Schema 校验

3. Tool 驱动:工具同时返回真实数据和 UI 描述

增量 Patch 的问题来自时序割裂:模型先返回卡片骨架,数据后补。更干净的方案是让 Tool 直接负责数据获取和 UI 描述。

用户问“推荐一款跑步手表”时,Agent 不让模型编商品数据,而是调用 searchProducts 工具。工具访问真实商品、推荐、价格、库存服务,然后返回结构化结果和 UI 描述。

sequenceDiagram
    participant C as 前端
    participant A as Agent
    participant M as LLM
    participant T as searchProducts Tool
    participant S as 商品/推荐服务

    C->>A: 推荐一款跑步手表
    A->>M: 识别意图并规划工具调用
    M-->>A: 调用 searchProducts
    A->>T: 执行工具
    T->>S: 查询商品、价格、库存、推荐理由
    S-->>T: 返回真实业务数据
    T-->>A: 返回数据 + UI 描述
    A-->>C: 推送标准消息
    C-->>C: 渲染商品卡片

工具返回值可以长这样:

{
  "toolId": "searchProducts",
  "content": "找到 3 款适合跑步的运动手表",
  "ui": {
    "type": "ProductList",
    "version": "1.0",
    "data": {
      "items": [
        {
          "id": "12345",
          "title": "运动智能手表 Pro",
          "price": 699,
          "imageUrl": "https://example.com/watch.jpg",
          "reason": "支持 GPS、心率监测和长续航"
        }
      ]
    }
  }
}

这样做有几个好处:

  • 真实数据来自业务服务,模型不负责生成价格、库存等高风险字段;
  • UI 描述和数据一起返回,没有先占位后补数据的体验断裂;
  • 业务方维护自己的 Tool 和卡片 Schema,Agent 只负责意图理解和编排;
  • 卡片内分页、筛选、收藏、加购等交互可以继续回调 Tool 或业务 API。

社区里有两类相关协议值得对比:MCP Apps 和 A2UI。


4. MCP Apps 与 A2UI 的区别

MCP(Model Context Protocol,模型上下文协议)用于让 Agent 安全连接外部工具、数据源和工作流。MCP Apps 可以理解为在 MCP 工具结果上增加 UI 展示能力。

A2UI(Agent to UI,Agent 到 UI 协议)则更关注声明式 UI 描述。它不绑定具体工具链,只定义“界面应该长什么样”。

协议驱动方式核心思想粒度适合场景
MCP AppsTool 驱动Tool 返回结果时附带 UI 展示信息工具级工具和卡片关系固定,例如商品搜索对应商品列表
A2UIAgent 驱动Agent 输出标准 JSON UI,各端按 Schema 渲染组件级需要动态组合表单、列表、图表、布局
组合使用Tool + UI SchemaTool 负责确定数据,A2UI 负责标准化 UI 描述工具级 + 组件级既要数据可靠,又要 UI 跨端通用

两者不是互斥关系。一个可落地的组合方式是:Tool 层采用 MCP Apps 的注册和调用机制,UI 层采用 A2UI 风格的 JSON Schema。这样既能保证数据来源确定,又能让 Web、iOS、Android 使用同一份 UI 描述。


三、用四层协议收敛多团队协作

当只有一个 Agent、一个前端页面、几种卡片时,任何方案都能跑起来。复杂度会在业务规模扩大后爆发:

  • A 团队用代码块扩展,B 团队用占位符;
  • C 团队消息格式叫 text,D 团队叫 markdown
  • Web 端点击按钮触发 addToCart,iOS 端叫 cart.add
  • 新增一张卡片时,多个端都要单独沟通字段含义。

协议的作用不是让系统变“高级”,而是给每一层输入输出建立稳定契约。卡片式对话可以拆成四层协议。

协议层解决的问题核心约定
Markdown 标记协议卡片在文本流中怎么写使用哪种扩展语法、组件名如何命名、JSON 如何校验
消息传输协议前后端之间传什么流式消息、全量消息、增量 Patch、推荐追问等统一格式
UI 渲染协议卡片长什么样用标准 JSON Schema 描述组件、布局和数据
事件通信协议用户交互后怎么办按统一 Action 格式处理跳转、API、Toast、Dialog、Tool 回调

四层协议之间的关系如下:

flowchart TB
    A[LLM / Agent 输出] --> B[1. Markdown 标记协议]
    B --> C[2. 消息传输协议]
    C --> D[3. UI 渲染协议]
    D --> E[Web / iOS / Android 渲染器]
    E --> F[用户点击、输入、提交]
    F --> G[4. 事件通信协议]
    G --> H[业务 API / Tool / Agent]
    H --> C

1. Markdown 标记协议:统一“怎么写卡片”

这一层要把卡片嵌入方式固定下来,例如统一采用代码块扩展:

```ProductCard
{
  "id": "12345",
  "title": "商品名称",
  "price": 99
}
```

同时定义几类规范:

规范示例
组件命名ProductCardFlightCardUserProfileCard
字段命名统一使用 camelCase
数据格式必须是合法 JSON
组件注册所有可渲染组件进入白名单
版本管理schemaVersion: "1.0"
降级策略未识别组件渲染为文本或兜底卡片

有了这层约束,LLM 的提示词、服务端校验、前端解析器、设计规范都能围绕同一种格式建设。


2. 消息传输协议:统一“怎么传”

对话系统通常是流式的。前端收到的不一定是一整段完整回答,而是一系列增量消息。消息传输协议要定义每个数据包的结构。

一个通用消息包可以这样设计:

{
  "messageId": "msg_001",
  "seq": 12,
  "type": "append",
  "payload": {
    "markdown": "为你推荐这款商品:"
  }
}

常见 type 可以包括:

type含义
full返回完整消息,通常用于首包或重建状态
append向已有 Markdown 后追加文本
patch对已有消息做局部更新
replace替换整条消息
recommend/prompt返回追问建议
error返回错误状态
done表示当前回答结束

如果使用 SSE(Server-Sent Events,服务器发送事件),传输格式可以是:

data: {"messageId":"msg_001","seq":1,"type":"append","payload":{"markdown":"为你推荐:"}}

data: {"messageId":"msg_001","seq":2,"type":"append","payload":{"markdown":"\n```ProductCard\n"}}

data: {"messageId":"msg_001","seq":3,"type":"append","payload":{"markdown":"{\"id\":\"12345\"}\n```"}}

data: {"messageId":"msg_001","seq":4,"type":"done","payload":{}}

消息层至少要处理三个问题:

  1. 顺序seq 用来避免乱序应用。
  2. 幂等:重复收到同一包时不能重复追加。
  3. 恢复:前端断线后可以按 messageId 拉取完整状态。

3. UI 渲染协议:统一“怎么画”

Markdown 标记解决的是“卡片出现在文本哪里”,但卡片内部结构还需要一套可跨端执行的描述方式。

预设卡片阶段,可以给每类卡片定义固定 Schema:

{
  "type": "ProductCard",
  "schemaVersion": "1.0",
  "data": {
    "id": "12345",
    "title": "运动智能手表 Pro",
    "price": 699,
    "imageUrl": "https://example.com/watch.jpg"
  },
  "actions": [
    {
      "type": "api",
      "name": "addToCart",
      "params": {
        "itemId": "12345"
      }
    }
  ]
}

更进一步,可以走 A2UI 风格的通用组件描述:

{
  "type": "card",
  "children": [
    {
      "type": "image",
      "props": {
        "src": "https://example.com/watch.jpg",
        "aspectRatio": "1:1"
      }
    },
    {
      "type": "text",
      "props": {
        "value": "运动智能手表 Pro",
        "style": "title"
      }
    },
    {
      "type": "text",
      "props": {
        "value": "¥699",
        "style": "price"
      }
    },
    {
      "type": "button",
      "props": {
        "text": "加入购物车"
      },
      "action": {
        "type": "api",
        "name": "addToCart",
        "params": {
          "itemId": "12345"
        }
      }
    }
  ]
}

UI 渲染协议的演进通常分为两个阶段:

flowchart LR
    A[预设卡片阶段] --> B[固定组件 + 固定 Schema]
    B --> C[端侧渲染器稳定]
    C --> D[Agent 生成阶段]
    D --> E[通用组件 JSON Schema]
    E --> F[Agent 动态组合布局、表单、列表、图表]

预设卡片更可控,适合早期落地;通用 JSON UI 更灵活,适合模型能力和安全校验成熟后的动态界面生成。两者可以共用同一套渲染器底座,只是开放程度不同。


4. 事件通信协议:统一“点了之后怎么办”

卡片不能只展示,还要能交互。用户点击“加入购物车”“筛选价格”“查看更多”“提交表单”后,系统需要知道事件发给谁、参数是什么、结果怎么反馈。

关键原则是:卡片 JSON 只声明动作,不包含可执行代码。

不要让模型生成 JavaScript 代码,也不要把任意脚本塞进卡片。更安全的做法是让 JSON 描述“能做什么”,具体执行由端侧事件处理器完成。

例如一个按钮:

{
  "type": "button",
  "text": "加入购物车",
  "action": {
    "type": "api",
    "name": "addToCart",
    "params": {
      "itemId": "12345"
    },
    "feedback": {
      "successText": "已加购",
      "failureToast": "加购失败,请稍后重试"
    }
  }
}

交互流程如下:

sequenceDiagram
    participant U as 用户
    participant C as 卡片组件
    participant E as 事件处理器
    participant G as 网关 API
    participant A as Agent

    U->>C: 点击加入购物车
    C->>E: 派发 action
    E->>E: 校验 action 类型和参数
    E->>G: 调用 addToCart
    G-->>E: 返回结果
    alt 成功
        E-->>C: 更新按钮状态为已加购
    else 失败
        E-->>C: 展示错误提示
    end
    E-->>A: 可选:回传交互上下文

事件类型可以分层定义:

事件类型用途示例
navigate页面跳转打开商品详情页
api调用业务接口收藏、加购、提交表单
tool回调 Agent Tool重新筛选商品、查询下一页
toast轻提示展示操作成功或失败
dialog弹窗二次确认、权益说明
updateState局部状态更新按钮置灰、列表刷新

事件通信协议需要特别关注安全边界:

  • 只允许白名单 Action;
  • 参数必须经过 Schema 校验;
  • API 权限由端侧或网关控制;
  • 敏感操作需要二次确认;
  • 所有交互事件要带 traceId,方便排查链路问题。

四、落地时容易踩的坑

1. 不要相信模型生成的业务数据

模型可以生成 UI 结构建议,但价格、库存、权益、订单状态必须来自真实系统。涉及交易、履约、账户、权限的数据都应该走 Tool 或业务 API。

2. 流式解析要有中间态

卡片代码块没闭合时,不要急着解析 JSON。前端需要明确区分三种状态:

状态行为
普通文本流直接渲染 Markdown
卡片缓冲中暂存 token,不解析 JSON
卡片完成校验 Schema,渲染组件或降级

3. Schema 必须版本化

卡片字段一定会变化。没有版本号时,前端无法判断字段缺失是老版本还是错误数据。

推荐格式:

{
  "type": "ProductCard",
  "schemaVersion": "1.1",
  "data": {}
}

旧版本继续兼容,新版本按能力逐步启用。

4. 跨端渲染不要依赖 Web 专属能力

如果目标包括 iOS 和 Android,UI Schema 不能绑定 CSS、DOM、浏览器事件等 Web 专属概念。更稳妥的方式是描述抽象组件和设计 token,例如 text.titlecolor.primaryspacing.medium

5. 降级策略要从一开始设计

卡片渲染失败不应该导致整条消息不可读。常见降级方式包括:

  • 卡片 JSON 无法解析:展示为普通文本;
  • 组件未注册:展示兜底卡片;
  • 数据超时:展示骨架屏和重试按钮;
  • Action 不支持:隐藏按钮或置灰;
  • Schema 版本过高:使用兼容字段渲染基础信息。

五、协议选型建议

不同阶段可以采用不同方案,不必一开始就追求完整的 Agentic UI。

阶段推荐方案原因
Demo 验证代码块扩展 + 模型直出快速验证卡片展示效果
小规模业务代码块扩展 + 服务端补数据数据更可靠,改造成本可控
多业务接入四层协议 + Schema 校验避免各业务方格式分裂
复杂交互Tool 驱动 + 事件通信协议数据、UI、交互闭环更清晰
跨端动态 UIMCP Apps + A2UI 风格 SchemaTool 保证确定性,Schema 保证跨端渲染一致

核心方向是:让模型负责意图理解和对话编排,让 Tool 负责真实数据,让协议负责跨端一致性和交互安全。

当 Markdown 标记、消息传输、UI 渲染、事件通信四层都稳定后,新业务接入就不再需要重新定义一套链路。业务方只要提供 Tool、Schema 和卡片组件,Agent 和前端都能按统一协议工作。


参考链接


评论