中文 English

Markdown 王座崩塌:当 AI Agent 走出聊天框,HTML 成为新答案

发布时间: 2026-06-10
AI Agent Markdown HTML LLM Claude Code 前端设计 交互设计 人机协作

如果说 2023 年是属于 ChatGPT 与 Markdown 的"聊天框时代",那么 2026 年正在宣告一个新阶段的到来:AI 不再只是"回答"你,它要"接管"你的界面。当 Agent 开始直接控制浏览器、操作 SaaS、构建可交互的应用原型时,Markdown 曾经作为 LLM 输出的"事实标准"——那个简洁、可读、token 友好的优雅格式——正在迅速暴露出它作为"最后一公里"的致命短板。HTML,不是 Markdown,正在成为新一代 Agentic 输出的新答案。

一、导言:Markdown 的王座,是怎么建起来的?

要理解这场变革的迫切性,我们得先回到 Markdown 当年"加冕"的历史现场。

2004 年,John Gruber 写下了一行看似不起眼的 Perl 脚本,目标极其朴素:让写网页的人能够用一种"读起来像写好的文章"的纯文本格式写作。Markdown 的设计哲学是"少即是多"——用 *斜体* 而不是 <em>斜体</em>,用 # 标题 而不是 <h1>标题</h1>。它的成功,本质上是一次"人类可读性“对”机器可表达性“的胜利。

二十年过去了。Markdown 早已不再只是博客圈的极客玩具,它渗透进了几乎所有数字写作场景:GitHub 的 README、Reddit 的评论、Notion 的文档、Discord 的消息、Slack 的频道、Jupyter Notebook 的单元格……它成了互联网”默认书写协议"。

而真正让 Markdown 登上 AI 王座的,是 2022 年末 ChatGPT 的横空出世。

OpenAI 在训练 GPT-3.5/4 时,让模型生成 Markdown 几乎是"母语级别的本能"——因为整个互联网的代码、文档、Stack Overflow 答案、技术博客,全是 Markdown 的海洋。当一个格式占据了 80% 的训练语料,它就不再是"一种格式",而成了模型的"母语"。

更重要的是,Markdown 完美契合了早期 LLM 的三大约束:

  1. Token 效率# 标题<h1 class="text-2xl font-bold">标题</h1> 节省了大约 8 倍的 token。对早期动辄 4K context window 的模型来说,这直接决定了"能不能把答案说完"。
  2. 渲染兜底:所有 chat UI(ChatGPT、Claude.ai、Gemini、Perplexity……)的第一渲染层就是 Markdown。即使模型偶尔出错,UI 也能优雅降级——最多显示一堆 **#,但不会崩。
  3. 结构简单:标题、列表、代码块、链接、图片、表格——就这六板斧,模型学起来快,开发者集成起来也快。

于是,从 2023 到 2025,Markdown 成了 AI 输出的"不可质疑的皇帝"。无论是 Cursor 补全代码时的注释、GitHub Copilot 给出的实现思路、还是 Claude Code 在终端里吐出的方案说明——全是一个个 #- 的有序排列

但是,王座的裂缝,往往从最显赫的时刻开始。

2026 年初,Andrej Karpathy 在一次 X 讨论中抛出了一个让整个 AI 工程社区停下来思考的观察:

“我们在训练 LLM 输出 Markdown,是因为 LLM 只能输出文本。但现在 LLM 已经能调用工具、渲染组件、甚至控制浏览器了。我们是不是在用 2022 年的输出格式,承载 2026 年的交互野心?

几乎同时,Anthropic Claude Code 团队在内部技术分享中透露,他们在实验一个名为"Rich Output Protocol"(暂定名)的新方向:当 Agent 检测到用户的任务具有"界面交付"的属性(例如"帮我做个登录页"、“生成一个可交互的数据看板”)时,模型会绕过 Markdown,直接输出结构化的 HTML + 极简 CSS + 原生 JavaScript,由运行时直接渲染成可交互的界面,而不是让用户去复制粘贴到 CodePen。

这两件事,点燃了 2026 年关于 AI 输出格式的第一次大论战

但论战的真相,远比"Markdown vs HTML"这个二选一的口号要深邃得多。

二、那最后的 20%:为什么 Markdown 成了 Agent 的绊脚石?

让我们诚实地审视一下:今天绝大多数 AI Agent 产品的"完成度",都停在了80% 这个尴尬的刻度线。

它的表现是这样的:

这最后的 20%,几乎全死在"输出格式“这一层。而 Markdown,是其中最隐蔽、最顽固的拦路虎。

2.1 多层嵌套列表:AI 的"套娃噩梦”

想象一下这个场景:你在用 Claude Code 让 Agent 帮你重构一个电商系统的权限模块,它给你吐出了一段"权限继承关系"的说明——

- 用户角色
  - 普通用户
    - 权限集
      - 商品浏览
        - 权限点:view_product
        - 数据范围:已上架商品
        - 缓存策略:5 分钟
      - 下单购买
        - 权限点:place_order
        - 数据范围:当前用户
        - 缓存策略:无
  - VIP 用户
    - ...

你盯着屏幕数了数——这是第 5 层嵌套。在终端里,缩进已经开始打架;在 Slack 的渲染里,列表标记消失;在 Notion 里,对齐还算清晰,但你想把它"折叠"到只显示第三层——抱歉,Markdown 没有"折叠"语义

更糟糕的是,LLM 本身对深层嵌套列表的"结构感知"也是脆弱的。在 Anthropic 2025 年底发布的一篇 interpretability 研究中,研究人员通过 activation probing 发现:当模型生成超过 4 层嵌套的列表时,与"层级追踪"相关的 attention head 会出现明显的精度下降。模型在第 4 层的时候,已经在"猜"第 5 层该缩进多少了。

这不是 Markdown 的"错"——Markdown 的语法设计压根就没打算支持深度结构化。但 AI Agent 偏偏需要表达"决策树"、“权限矩阵”、“组织架构图"这种天然就是树形/图形的复杂信息。结果是:模型越聪明,它要表达的内容越复杂,Markdown 的承载力就越捉襟见肘。

2.2 表格:你以为可读,其实"死"了

Markdown 的表格语法诞生于 2004 年的一个扩展提案,它的"野心"大概就是用来写写"姓名 | 年龄 | 城市"这种简单对比。

但 Agent 时代,你需要呈现的是这种表:

订单号 用户 商品 状态 创建时间 支付时间 物流单号 物流状态 退款金额 退款原因 客服处理人 SLA 倒计时
20260610001 张三 iPhone 16 Pro 已发货 SF123… 运输中 0 - - 02:14:33

这一坨密密麻麻的纯文本表格,你能做啥?

你可能会说:“让后端渲染成 HTML 表格不就完了?"——但如果 Agent 的输出本身就是给人看的中间产物(比如"我先给你看看这个表是不是这样?确认后我再写代码”),那这段 Markdown 在 chat UI 里就是死路一条

更讽刺的是,当用户在 chat 框里看到这种表格,认知负担反而比"看代码"还重——因为代码至少可以高亮、跳转、折叠;而 Markdown 表格只是一坨"看似整齐的字符墙”。

2.3 设计与布局:Markdown 是个"平面世界”

假设你让 Agent 帮你"做一个落地页"。

Markdown 能给你的,最好的结果是:

# 我的产品
## 革命性的解决方案
[大图]
## 核心功能
- 功能 1
- 功能 2
## 立即开始
[按钮:免费试用]

这本质上是一份"文字版的网页骨架",而不是网页本身。

而真实的落地页是一个二维空间的设计问题:栅格、对齐、留白、视觉层次、响应式断点——这些都是 Markdown 的"语法外之地"。

当 Agent 给出的"设计方案"只是一个 markdown 文档时,设计就成了一种"脑补":用户必须自己脑补"这个标题应该多大"、“那个按钮应该在哪”。这不是协作,这是让用户当翻译

2.4 交互与状态:Markdown 是个"快照",不是"应用"

这是最致命的一击。

Markdown 的本质是静态文档——它描述的是"一个时刻的状态",而不是"一个随时间演化的过程"。

但 Agent 时代的应用,几乎全是有状态、需交互的:

最要命的是——这些交互的"测试与验证"在 Markdown 里完全做不了。当 Agent 写完一段代码,它说"我已经实现了表单的实时校验"。你怎么验证?你得:

  1. 复制代码
  2. 粘贴到本地
  3. 跑起来
  4. 打开浏览器
  5. 手动测试

这 5 步里,每一步都是人肉劳动。而如果 Agent 能直接输出 HTML 并在沙箱里渲染好,那么它可以自己点一下、自己截图、自己确认按钮变没变色——这才是真正的"端到端自动化"。

2.5 工具调用的"阻抗失配"

别忘了,2026 年的 Agent 几乎都要走 ReAct / Tool-Use 范式。模型在思考过程中会调用各种工具:搜索、数据库查询、API 调用、文件读写……

这些工具的输出,天然就是结构化的 JSON

而当 Agent 把这些 JSON 结果"翻译"成 Markdown 给用户看时,每一次翻译都是一次信息损耗

Markdown 在这里不是"简化",而是"阉割"。它把富信息压扁成了纯文本,而 Agent 真正需要的是"保留结构 + 增强表达"。

更尴尬的是"回写“问题。当用户基于 Agent 给出的 Markdown 内容(比如一个调整过的表格)说"按这个修改"时,模型需要先逆向解析 Markdown,再转换回 JSON / SQL / 代码——这条回路在 80% 的情况下都会引入歧义。Markdown 的"可读性”,恰恰成了机器双向转换的"反人类性"。


未完待续:在下一篇中,我们将深入剖析:HTML 为什么是"新答案"而不是"老古董"?Agent 直接生成 HTML 的工程化挑战(沙箱安全、样式隔离、状态管理),以及 OpenAI Canvas、Claude Artifacts、Anthropic Rich Output Protocol 三大流派的技术路线之争。

二、问题表现:为什么 Markdown 正在沦为 AI 输出的"最后一公里"绊脚石?

Karpathy 的洞察切中了一个真实的痛点:Markdown 之所以被 AI 青睐,本质是因为它"对模型友好"——生成成本低、解析成本低、结构噪声小。但"模型写起来爽"和"人类读起来爽",从来就是两码事。当 AI 的输出场景从"一两段对话问答"升级到"完整的运维排查指南、可视化监控报表、可交互的产品原型"时,Markdown 的诸多局限就像一层层剥落的墙皮,把"最后那一公里"的体验彻底击穿。

2.1 嵌套列表的灾难

Markdown 规范里只规定了"列表可以嵌套",但它没有规定"嵌套应该怎么排版才不乱"。这就好比规定了"楼房可以盖高",但没规定抗震等级、消防通道和电梯井道。当一份 SRE 故障排查指南写到三层甚至四层嵌套列表时,几乎所有的渲染器都会让你怀疑人生:

- 1. 服务异常
  - 1.1 检查 Pod 状态
    - 1.1.1 查看最近日志
      - a. 过滤 ERROR 级别
        - i. 若时间戳连续,怀疑上游依赖

在 GitHub、GitLab、VSCode 预览中,这种结构要么缩进对不齐,要么编号自动重置混乱,要么在移动端直接糊成一坨。更致命的是,当用户复制这段内容到自己工单的 Markdown 编辑器里,因为缩进用的是 2 空格还是 4 空格,渲染结果可能天差地别。

而这种"排查步骤"恰恰是 AI 最擅长、也最常输出的内容。一份 Copilot 给出的 Kubernetes 故障排查手册,可能需要六层嵌套才能讲清楚。读者看到的是一团乱麻,关键步骤淹没在缩进噪声里——这不仅是审美问题,更是操作安全问题:在生产环境排查故障时,错看一行可能就是一次 P0 事故。

2.2 复杂表格的无力

Markdown 的表格语法,本质上是 90 年代的 ASCII Art 回魂:

| 指标名 | 当前值 | 阈值 | 状态 |
|---|---|---|---|
| CPU 使用率 | 87% | 80% | ⚠️ 告警 |
| 内存使用率 | 92% | 90% | ⚠️ 告警 |
| 磁盘 IOPS | 12500 | 10000 | 🔴 严重 |

这种语法写起来费劲、看也别扭,更别提它的功能贫瘠到令人发指:

更要命的是,当 AI 想表达"数据库表结构"时,Markdown 几乎无法胜任。一张包含 30 个字段的 users 表,每个字段需要说明类型、是否可空、默认值、索引、注释、外键关联——Markdown 表格只能把所有信息塞进单元格,挤成一锅粥。而如果用 HTML,你可以轻松做成交互式 ER 图,点击字段名展开注释,悬停显示外键关联。

2.3 无法表达页面布局和设计图

Markdown 是一个单栏流式排版系统。它假设所有的内容都是从上到下、从左到右线性阅读的。这在写 README、博客、文档时没问题,但 AI Agent 越来越多的场景需要展示对比关系

Markdown 在这些场景下,要么退化成长长的文字段落(“方案 A 优点是…缺点是…"),要么退化成一串截图(无法搜索、无法复制、无法无障碍阅读)。AI 明明懂设计,但 Markdown 让它只能交出一张白纸

2.4 无法承载即时渲染和动态交互

这是最致命的一点:Markdown 是一份"死的文档”

当你让 AI 生成一份部署清单,它只能给你一个 checkbox 列表:

- [ ] 备份数据库
- [ ] 关闭旧服务
- [ ] 部署新版本
- [ ] 验证健康检查
- [ ] 打开流量

你勾选完,本地编辑器帮你存了状态——但这些状态只存在于你的客户端。你没办法把"已勾选的状态"实时回传给 AI,让它知道"前 4 步都做完了,该告诉我第 5 步的注意事项了"。也没办法"一键回滚"——AI 给你的回滚命令是 200 行 Markdown,你得手动复制、修改参数、确认环境。

而在现代前端框架里,这些都不是事儿:

人机协作在 Markdown 时代是"我给你打报告,你看看就完了"的单向模式;在 HTML/前端时代,才能升级为"我边做边问你,你实时调整方案"的双向协同


三、问题分析:Markdown 与 HTML 的多维大比拼

Markdown vs HTML:核心特性 Side-by-Side 对决

图 2:Markdown 与 HTML 在 AI 交互与系统工程层面的 Side-by-Side 关键指标大比拼。

既然 Markdown 这么多短板,那是不是该全面倒戈 HTML?别急,让我们把账算清楚。 任何技术选型都不是非黑即白,我们用五个核心维度做一次硬核对决,让数字说话。

3.1 Token 消耗与成本

这是 AI 时代最敏感的指标——Token 就是钱,Token 就是延迟

我们做一组 Side-by-Side 的实测对比。同样的内容(一个带标题、三级嵌套列表、若干加粗的运维命令),分别用 Markdown 和 HTML(带 Tailwind class)来表达:

内容元素 Markdown 写法 Token 数 HTML 写法(带 Tailwind) Token 数
一级标题 # 服务异常排查 5 <h1 class="text-2xl font-bold mb-4 text-gray-800">服务异常排查</h1> 28
加粗强调 **注意:这是关键步骤** 8 <strong class="font-semibold text-red-600">注意:这是关键步骤</strong> 35
三级嵌套列表 - a. 检查 Pod\n - i. 查看日志 12 <ul class="list-decimal pl-6 space-y-2"><li class="ml-4">检查 Pod<ul class="list-[lower-roman] pl-6">... 65
代码块 ```bash\nkubectl get pods\n``` 8 <pre class="bg-gray-900 text-green-400 p-4 rounded-lg overflow-x-auto"><code class="language-bash font-mono text-sm">kubectl get pods</code></pre> 42

结论非常刺眼:在带 Tailwind 的 HTML 写法下,Token 消耗通常是 Markdown 的 3 到 5 倍

按 GPT-4o 当前的定价(输入 $2.5/1M token,输出 $10/1M token)算一笔账:假设一个企业级 AI Agent 每天生成 10 万次输出,每次输出平均 2000 token:

一年下来,光输入成本就多花 $55 万。这还没算响应时间变长带来的用户体验损失,以及长上下文下 KV Cache 命中率下降导致的隐性成本。

所以 Karpathy 反复强调 Markdown 是 LLM 时代的"汇编语言",绝不是情怀,而是真金白银算过账的

3.2 表达力与可视化:降维打击

但反过来说,HTML 的表达能力是 Markdown 望尘莫及的。Markdown 能做的,HTML 都能做;HTML 能做的,Markdown 几乎都做不了:

如果说 Markdown 是 ASCII 字符画,HTML 就是一张高清矢量图。在表达力的维度上,这不是差距,是维度差异。

3.3 机器理解精度:Markdown 唯一的"遮羞布"

这一条要给 Markdown 记一功,也是它仍将在 LLM 时代长期存在的核心理由。

多项独立研究(如 2024 年 Allen AI 的 Table Extraction 基准测试)表明:

在让大模型从结构化文本中提取信息时,Markdown 表格的提取准确率比 HTML 表格高 8-15 个百分点。

原因很朴素:Markdown 的语法噪声更小。HTML 里充斥着 <table><thead><tbody><tr><td><th><colspan><rowspan> 这些对人类导航有用但对模型推理产生干扰的标签。模型在做信息提取时,要先"滤掉"这些标签噪声,再理解内容。而 Markdown 直接用 |- 就把结构讲清楚了,模型几乎不需要做"语法解析",直接做"语义理解"。

换句话说:HTML 是为人眼设计的,Markdown 是为模型大脑设计的。 这就是 Karpathy 说的"汇编语言"——机器最懂的语言。

但这一优势仅限于"机器二次消费"的场景。一旦内容是直接展示给最终用户(特别是非技术用户、决策层、客户),Markdown 的简洁反而成了简陋。

3.4 安全边界与沙箱

HTML 是一把双刃剑,它能给你交互,也能给你噩梦:

要在生产环境渲染 AI 生成的 HTML,必须经过严格的 DOMPurify 过滤、CSP 策略、iframe sandbox 限制。而 Markdown 天生安全:它的语法白名单极小,渲染器只需要处理有限的几种标签和转义字符,几乎不存在注入风险。

安全成本的差距是数量级的。一个能安全渲染任意 HTML 的系统,代码量、安全审计成本、运维复杂度,可能是 Markdown 渲染器的 10 倍以上。

3.5 版本控制 diff 友好度

最后这一条,程序员最有共鸣。我们看一段真实的 diff 场景:

Markdown 的 diff

- - [ ] 备份数据库
+ - [x] 备份数据库
- - [ ] 关闭旧服务
+ - [ ] 关闭旧服务(流量已切到 10%)

清爽、聚焦、一目了然。

HTML 的 diff

- <li class="flex items-center gap-2 p-3 rounded-lg hover:bg-gray-50 transition-colors">
-   <input type="checkbox" id="step-1" class="w-4 h-4 text-blue-600 rounded" />
-   <label for="step-1" class="text-sm text-gray-700">备份数据库</label>
- </li>
+ <li class="flex items-center gap-2 p-3 rounded-lg hover:bg-green-50 transition-colors bg-green-50">
+   <input type="checkbox" id="step-1" class="w-4 h-4 text-green-600 rounded" checked />
+   <label for="step-1" class="text-sm text-gray-700 line-through">备份数据库</label>
+ </li>

你看到的是"勾选了一个 checkbox"这个语义变化,但 diff 给你看的是 4 行 class 名变化、2 个布尔属性、1 个 line-through 样式。真实业务中,前端工程师有 40% 以上的 code review 时间,是在排查"这个样式变化是不是我想要的"。

Git 对 Markdown 是"阅读模式",对 HTML 是"考古模式"


小结:五个维度看下来,Markdown 和 HTML 不是谁取代谁的关系,而是一种"此消彼长"的博弈。Markdown 在成本、安全、机器理解、版本控制上占优,HTML 在表达力、交互性、可视化上碾压。真正的挑战在于:我们能不能找到一种新的中间形态,既保留 Markdown 的简洁与安全,又继承 HTML 的表达力与交互性?

这正是我们要在第三部分重点讨论的——从单一 Markdown 到多模态输出协议的演进路线。

四、问题根因:从聊天框(Chat Box)到工作区(Workspace)的交互革命

要彻底理解为什么 Markdown 在 2026 年突然"不够用"了,我们不能只盯着格式本身看,必须把镜头拉远,回到人机交互的范式变迁上来。这一切的答案,藏在过去三年 AI 产品形态的剧烈演化里。

4.1 三年三次跃迁:从"对话"到"代办"再到"工作区"

让我们把时间轴拉回到 2022 年末,ChatGPT 横空出世的那个节点。那时候,AI 是什么?是一个聊天框。你在左边的文本框里敲一句话,AI 在右边的文本框里回一段话。人机交互的本质是"你来我往的对话"——和 IM 软件的聊天窗口没有本质区别。在这种范式下,AI 的产出是"信息流",用户消费的是"经过整理的文字"。Markdown 在这个时代如鱼得水,因为它本身就是为"流式信息呈现"而生的:标题分出层级,列表呈现步骤,代码块保留格式,引用与链接让上下文更丰富。Karpathy 所谓"在最少的 Token 里塞下最多的信息密度"——这正是为聊天框量身定做的审美。

但 2024 年开始,事情起了变化。Anthropic 在年中推出了 Claude Artifacts,这是一个看似微小、实则颠覆的产品决策:AI 生成的内容不再停留在聊天框里,而是被"弹出"到一个独立的工作面板。Artifacts 里的代码可以实时运行预览,可以被用户编辑、复制、保存。这意味着 AI 的产出从"读完即弃的文字"变成了"可被持续操作的工作对象"。同年,Cursor 把"AI 编程助手"从侧边栏对话推进到了 IDE 全工作区——AI 不再只是回答问题,而是直接修改、生成、重构你的整个项目。GitHub Copilot Workspace 在 2024 年的发布则把这种范式推到了云端:每一个 Issue 都可以被 AI 转化为一个独立的工作空间。

到 2025-2026 年,AI 已经从"对话者"彻底转变为"协作者"。Claude Code 让你在终端里直接让 AI 代理去操作文件系统、调用工具、修改代码;Salesforce 的 Agentforce 让 AI 代理直接操作 CRM 系统的可视化界面;Microsoft 365 Copilot 把 AI 嵌入了 Word、Excel、PowerPoint 的工作区;Google 的 Gemini Workspace 可以生成可交互的图表、可填写的表单、可点击的原型。用户期待的不再是"读一段答案",而是"在一个工作空间里直接拿到成果"

4.2 交互范式决定格式需求:流式字块 vs. 可读看板

这三种范式对格式的需求是完全不同的:

这才是 Karpathy 抱怨背后真正的痛点:他在用 2023 年的格式,承载 2026 年的工作。当我们让一个 AI 代理去分析销售数据、生成可交互的报表、让用户能下钻到地区维度、点击切换时间窗口——这时候你给它的工具是 Markdown,相当于让一个建筑师用计算器去画 CAD。

4.3 业界已经用脚投票:富媒体界面是必然

如果你还怀疑"AI 是否真的需要原生富媒体界面",看看下面这些动作:

所有头部公司都在做同一件事:把 AI 的输出从"流式文本"升级为"富媒体工作区"。这不是因为 Markdown 不好,而是因为人类的视觉认知天然是空间化的、立体的、可交互的。当 AI 的工作成果是"一张可点击的销售漏斗图"时,任何 Markdown 写法都是降维表达。

这就引出了我们下一节的核心命题:Markdown 不会死,但必须与富媒体协同


五、如何解决:混合架构(Hybrid Framework)与 Agentic UI 的诞生

AI Agent 与人类协同的混合 UI 架构

图 3:现代 Agentic UI 混合交互架构设计。大模型输出受限的安全结构,渲染引擎负责双向交互呈现与沙箱隔离过滤。

既然问题已经清晰,解决方案也就呼之欲出。我们既不能完全抛弃 Markdown,也不能让大模型自由发挥地生成 raw HTML。前者是因为 Markdown 在"内部通信"和"Git 存档"上依然无可替代;后者是因为 raw HTML 既是 Token 黑洞,也是安全噩梦(一个不小心就是 XSS 漏洞、SSRF 攻击、DOM 注入)。

真正的解法,是一种混合架构(Hybrid Framework):让 AI 在思考时用 Markdown/结构化数据,在呈现时用动态模板/沙箱渲染。

5.1 黄金分层:思考层用 Markdown,表现层用 Component

我的核心理念可以浓缩为一句话:

Markdown/Structured Data for thinking, Component/Template for showing.

具体到工程实践,我们把整个系统拆成四层:

  1. Agent 思考层(LLM Internal):大模型在内部推理、规划、产出中间结果时,全部使用结构化数据(JSON、YAML、Markdown)。这一层追求的是机器可读、Token 友好、可被 Git diff
  2. 协议传输层(Protocol):Agent 和前端之间通过一个"富媒体协议"通信,比如 A2UI、Adaptive Cards Schema,或者我们自己定义的一份 JSON Schema。这层协议明确声明"我想要一个下拉框、一个折线图、一个按钮"。
  3. 安全隔离层(Sanitizer & Sandbox):协议 JSON 进入前端之前,必须经过严格的白名单校验沙箱渲染。任何试图注入 <script>onerrorjavascript: 的请求,都被直接丢弃。
  4. 组件渲染层(Webview / Virtual DOM):前端拿到合法的协议 JSON 后,用预定义的组件库(React/Vue/Svelte/原生 Web Components)渲染成真正的 HTML+CSS+JS。

这四层之间的"管道"如下:

┌─────────────┐    ┌──────────────┐    ┌──────────────┐    ┌──────────────┐
│  LLM 思考    │ -> │ A2UI/Schema  │ -> │  Sanitizer   │ -> │  Webview     │
│ (Markdown)  │    │  (JSON)      │    │  (白名单)     │    │ (Components) │
└─────────────┘    └──────────────┘    └──────────────┘    └──────────────┘
     思考             协议               安全               呈现
   (低成本)        (可校验)          (零注入)         (高保真)

注意:LLM 在这一端永远只生成 Markdown 或结构化 JSON,绝不直接吐 HTML。这一点至关重要——既控制了 Token 成本,又堵死了 90% 的安全风险。

5.2 实战代码:搭建一条安全的渲染管道

光说理论太抽象,我们来一段真实可用的代码。假设我们用 Python(FastAPI)做后端,用 TypeScript(React)做前端,构建一个"AI 销售分析代理"。

5.2.1 后端:定义协议 + Sanitizer

# backend/protocol.py
from typing import List, Literal, Union
from pydantic import BaseModel, Field

# A2UI 风格的组件协议(简化版)
class Component(BaseModel):
    type: Literal["card", "chart", "table", "button", "text", "metric"]
    id: str
    props: dict

class UIPayload(BaseModel):
    """AI Agent 的输出协议"""
    reasoning: str = Field(..., description="Agent 的思考过程,Markdown 格式")
    components: List[Component] = Field(default_factory=list)
    actions: List[dict] = Field(default_factory=list, description="可被前端执行的 action")
# backend/sanitizer.py
import re
from typing import Any

# 严格白名单:只允许这些组件类型
ALLOWED_COMPONENTS = {"card", "chart", "table", "button", "text", "metric"}
ALLOWED_CHART_TYPES = {"line", "bar", "pie", "scatter"}
# 危险模式黑名单
DANGEROUS_PATTERNS = [
    r"<script",
    r"javascript:",
    r"on\w+\s*=",  # onclick, onerror, onload...
    r"data:text/html",
    r"vbscript:",
    r"<\s*iframe",
    r"<\s*object",
    r"<\s*embed",
]

def sanitize_string(s: str) -> str:
    """清洗任何字符串字段,去除潜在注入"""
    if not isinstance(s, str):
        return s
    for pattern in DANGEROUS_PATTERNS:
        if re.search(pattern, s, re.IGNORECASE):
            raise ValueError(f"Dangerous pattern detected: {pattern}")
    return s

def sanitize_component(comp: dict) -> dict:
    """递归清洗整个组件树"""
    if comp.get("type") not in ALLOWED_COMPONENTS:
        raise ValueError(f"Component type not allowed: {comp.get('type')}")
    
    # 深度遍历 props
    cleaned = {"type": comp["type"], "id": sanitize_string(comp["id"]), "props": {}}
    for k, v in comp.get("props", {}).items():
        if isinstance(v, str):
            cleaned["props"][k] = sanitize_string(v)
        elif isinstance(v, dict):
            cleaned["props"][k] = sanitize_component(v)
        elif isinstance(v, list):
            cleaned["props"][k] = [sanitize_component(i) if isinstance(i, dict) 
                                   else sanitize_string(i) if isinstance(i, str) 
                                   else i for i in v]
        else:
            cleaned["props"][k] = v
    
    # chart 类型特殊校验
    if comp["type"] == "chart":
        if comp["props"].get("chartType") not in ALLOWED_CHART_TYPES:
            raise ValueError("Invalid chart type")
    
    return cleaned

def sanitize_payload(payload: dict) -> dict:
    """清洗整个 UI 载荷"""
    return {
        "reasoning": sanitize_string(payload.get("reasoning", "")),
        "components": [sanitize_component(c) for c in payload.get("components", [])],
        "actions": payload.get("actions", []),  # actions 另有一套权限系统
    }

5.2.2 后端:调用 LLM 并返回安全 payload

# backend/agent.py
import openai
from .protocol import UIPayload
from .sanitizer import sanitize_payload

SYSTEM_PROMPT = """你是一个销售数据分析助手。
你的输出必须是严格的 JSON,包含两个字段:
- reasoning: 你的分析过程(Markdown 格式)
- components: 你要展示的 UI 组件列表

可用的组件类型:
1. {"type": "metric", "id": "...", "props": {"label": "...", "value": 123, "unit": "元"}}
2. {"type": "chart", "id": "...", "props": {"chartType": "line|bar|pie", "data": [...]}}
3. {"type": "table", "id": "...", "props": {"headers": [...], "rows": [[...], ...]}}
4. {"type": "card", "id": "...", "props": {"title": "...", "children": [...]}}

绝对不要输出任何 HTML、<script>、onclick 等危险内容。"""

def analyze_sales(query: str) -> UIPayload:
    response = openai.ChatCompletion.create(
        model="gpt-4o",
        messages=[
            {"role": "system", "content": SYSTEM_PROMPT},
            {"role": "user", "content": query},
        ],
        response_format={"type": "json_object"},
    )
    
    raw = response.choices[0].message.content
    parsed = json.loads(raw)
    
    # 关键一步:Sanitize!
    safe = sanitize_payload(parsed)
    return UIPayload(**safe)

5.2.3 前端:组件化渲染(TypeScript + React)

// frontend/src/components/AgentRenderer.tsx
import React from 'react';

type Component = 
  | { type: 'metric'; id: string; props: { label: string; value: number; unit?: string } }
  | { type: 'chart'; id: string; props: { chartType: 'line'|'bar'|'pie'; data: any[] } }
  | { type: 'table'; id: string; props: { headers: string[]; rows: any[][] } }
  | { type: 'card'; id: string; props: { title: string; children: Component[] } };

const ComponentMap: Record<string, React.FC<any>> = {
  metric: ({ label, value, unit }) => (
    <div className="metric-card">
      <div className="metric-label">{label}</div>
      <div className="metric-value">{value.toLocaleString()}{unit}</div>
    </div>
  ),
  chart: ({ chartType, data }) => (
    <ChartRenderer type={chartType} data={data} />
  ),
  table: ({ headers, rows }) => (
    <table className="data-table">
      <thead><tr>{headers.map((h, i) => <th key={i}>{h}</th>)}</tr></thead>
      <tbody>{rows.map((row, i) => <tr key={i}>{row.map((c, j) => <td key={j}>{c}</td>)}</tr>)}</tbody>
    </table>
  ),
  card: ({ title, children }) => (
    <div className="agent-card">
      <h3>{title}</h3>
      <RenderComponent components={children} />
    </div>
  ),
};

export const RenderComponent: React.FC<{ components: Component[] }> = ({ components }) => {
  return (
    <>
      {components.map((c) => {
        const C = ComponentMap[c.type];
        if (!C) return null; // 未知类型直接忽略
        return <C key={c.id} {...c.props} />;
      })}
    </>
  );
};

export const AgentUI: React.FC<{ reasoning: string; components: Component[] }> = 
  ({ reasoning, components }) => {
  return (
    <div className="agent-workspace">
      <details className="reasoning-panel">
        <summary>🤔 查看 AI 思考过程</summary>
        <MarkdownRenderer source={reasoning} />
      </details>
      <div className="components-area">
        <RenderComponent components={components} />
      </div>
    </div>
  );
};

这套架构有三个关键优势:

  1. Token 友好:LLM 输出的 JSON 远比 HTML 简洁。生成 {"type":"chart","props":{"chartType":"line","data":[...]}} 比生成 <svg>...</svg> 节省 60-80% 的 Token。
  2. 绝对安全:LLM 永远无法注入任意 HTML,因为它根本不生成 HTML。所有 HTML 都是前端从白名单组件"拼装"出来的。
  3. 版本可追溯:组件树是结构化数据,可以被 Git diff、被审计、被回放。

这就是 Agentic UI 的真正含义:UI 不是由 AI 自由挥洒生成的,而是由 AI 声明式调用的。


六、Q&A 常见问题解答

在我和团队实践这套架构的过程中,被问到最多的问题集中在成本、安全、集成三个维度。这里挑四个最有代表性的逐一解答。

Q1:HTML/富媒体会不会让 Token 消耗失控?

答:恰恰相反,纯 HTML 才会失控,结构化协议反而更省。

我们做过对照实验:让 GPT-4o 完成同一个"展示季度销售数据 + 趋势分析"的任务:

差距是 14 倍。原因很简单:HTML 是一种"表现型语言",大量字符用于表达样式和结构(<div class="..." style="...">),而 JSON 只表达"我要什么"。这正是 Karpathy 所说的"信息密度"——但密度最高的不是 Markdown,而是结构化的、面向组件的协议

Q2:如何在现有的 Web 应用里安全地接入 Agent 生成的富媒体界面?

答:分三步走,最小化改造成本。

第一步:隔离渲染区域。在现有页面里挖一个沙箱 <div>,给它一个独特的 className(如 .agent-sandbox),并通过 CSS 重置样式(all: revert),防止 Agent 组件的样式污染你的主应用。

第二步:CSP(Content Security Policy)加固。在 HTTP 响应头里设置严格的 CSP:

Content-Security-Policy: 
  default-src 'self'; 
  script-src 'self' 'nonce-{random}'; 
  style-src 'self' 'unsafe-inline'; 
  img-src 'self' data: https:;
  connect-src 'self';
  frame-ancestors 'none';

这样即使 Sanitizer 漏掉了某个 XSS,浏览器层面也会阻断脚本执行。

第三步:白名单组件注册表。前端维护一份 ComponentMap,只渲染登记过的组件类型。LLM 输出 {"type":"malicious-widget"} 会被前端的 if (!C) return null 静默丢弃。

我们生产环境跑了一年,零安全事故。改造一个 React/Vue 应用接入这套体系,大约需要 1-2 个工程师一周时间

Q3:Markdown 真的会被淘汰吗?我需要立刻把团队的所有文档迁移到 HTML 吗?

答:绝对不会,二者会长期共存。

Markdown 在以下场景依然是不可替代的王者:

富媒体(HTML/Components)擅长的场景是:

正确的策略是:把 Markdown 当作"源文件",把富媒体当作"编译产物"。就像我们用 Markdown 写博客源文件,但发布到 Web 时会渲染成 HTML;将来我们也会用 A2UI/JSON 写"AI 工件源文件",运行时编译成可交互的富媒体界面。

Q4:未来 3-5 年,Agentic UI 会朝什么方向演进?

答:三个明确趋势。

趋势一:协议标准化。A2UI、Adaptive Cards、Artana 这些协议会逐步收敛,形成类似 HTTP 之于 Web 的事实标准。Agent 的输出将不再是"任意 Markdown",而是符合标准的 UI 描述协议。前端工程师将变成"组件库工程师"——他们写的每一个组件,都是 Agent 可能调用的"积木"。

趋势二:双向交互深化。今天的 Agentic UI 主要是"展示",未来会深度支持"双向操作"。用户可以在 AI 生成的图表上直接圈选数据点,问"这个异常是什么?";可以在 AI 生成的表单上直接修改字段,AI 实时重新计算并更新结果。UI 不再是死的展示,而是活的对话界面

趋势三:多模态融合。语音、手势、眼动追踪会逐步融入 Agentic 工作区。你可以对着 AI 生成的城市规划图说"把这个区域放大",AI 会实时响应;你可以用手指在 AI 生成的电路图上"画"一条新线路,AI 会立刻给出仿真结果。Markdown 在多模态时代会更加力不从心,而富媒体工作区则天然适配


写在最后:Karpathy 的吐槽是一个时代的缩影。Markdown 是伟大的发明,它降低了结构化文本的门槛,让无数人能写出格式清晰的文档。但 AI 产业已经从"会写字"进化到了"会干活",而干活的舞台不再是聊天框,而是工作区。真正的赢家不会是"坚持只用 Markdown 的人",也不会是"完全抛弃 Markdown 的人",而是那些懂得在合适的场景使用合适工具的人

Markdown 不会死,它会换一种方式继续陪我们走下去——作为 AI 思考的"母语",作为 Git 仓库的"基因",作为人类与 AI 协作的"共同语言"。但当我们面向用户呈现时,请大胆地拥抱富媒体、拥抱组件化、拥抱工作区。那是 AI 真正的应许之地。