LLM 驱动聊天界面的兴起造成了一个独特的用户体验问题:看着文本逐字符出现令人兴奋,但看着部分渲染的 Markdown 闪烁和跳动却令人沮丧。当 LLM 生成代码块、表格或嵌套列表时,标准的 Markdown 渲染器无法处理 Token 的增量到达。它们等待完整的输出,然后一次性渲染全部——这违背了流式的目的。用户在流式完成之前盯着原始文本,然后页面在一切同时重新格式化时跳动。
Streamdown 是 Vercel 对这个问题的优雅解决方案。它是一个专为 LLM 生成内容设计的开源流式 Markdown 渲染器。关键洞察在于 Markdown 渲染必须渐进地进行:每个 Token 应立即渲染,元素应在变得明确时出现,DOM 应逐步更新而不会出现布局不稳定。
该库是专为 AI 时代打造的。传统的 Markdown 渲染器假设完整、静态的输入。Streamdown 假设不完整、流式的输入,并对如何渲染部分内容做出智能决策。当 LLM 开始生成代码块时,Streamdown 立即渲染一个打开的代码块容器。当它开始一个表格时,它渲染开放的表格标签并在单元格到达时填充。这创造了一个流畅、渐进的视觉体验,与 LLM 响应的流式性质相匹配。
核心架构
Streamdown 的内部管线通过四个阶段处理传入的 Token:
| 阶段 | 组件 | 功能 |
|---|---|---|
| 词法分析 | 增量词法分析器 | 在 Token 到达时解析部分 Markdown Token |
| 构建器 | 部分 AST 构建器 | 构建可表示不完整元素的 AST |
| 渲染器 | 渐进式 DOM 渲染器 | 随着每次 AST 变更更新实时 DOM |
| 完成器 | 流式后解析器 | 最终化任何剩余的不完整元素 |
渲染管线
下图说明了 Streamdown 如何处理来自 LLM 的流式响应:
sequenceDiagram
participant LLM as LLM 流
participant Buffer as Token 缓冲区
participant Lexer as 增量词法分析器
participant AST as 部分 AST
participant DOM as DOM 渲染器
participant Worker as 高亮工作线程
LLM->>Buffer: Token "## "
Buffer->>Lexer: 刷新部分块
Lexer->>AST: "h2 标题"
AST->>DOM: 开启标题标签
LLM->>Buffer: Token "Installation"
Buffer->>Lexer: 刷新
Lexer->>AST: "文本节点"
AST->>DOM: 附加 "Installation"
LLM->>Buffer: Token "\n\n```python\n"
Buffer->>Lexer: 刷新
Lexer->>AST: "开启代码块"
AST->>DOM: 渲染代码块外壳
LLM->>Buffer: Token "print('hello')"
Buffer->>Lexer: 刷新
Lexer->>AST: "代码行"
AST->>DOM: 附加代码行(纯文本)
LLM->>Buffer: Token "\n```"
Buffer->>Lexer: 刷新
Lexer->>AST: "关闭代码块"
AST->>DOM: 关闭代码块
Worker-->>AST: 请求高亮
AST->>DOM: 使用高亮内容更新LLM 流中的每个 Token 实时通过此管线。增量词法分析器是关键组件:它必须在 Token 到达之间维护状态,以便正确识别部分序列(如 [Click h 可能是链接 [Click here](url) 的开头)并适当地渲染它。
渲染质量比较
下表将 Streamdown 与渲染 LLM 输出的替代方法进行了比较:
| 方法 | 流式? | 部分语法 | 代码高亮 | 表格渲染 | 包大小 |
|---|---|---|---|---|---|
| Streamdown | 是,逐个 Token | 优雅降级 | 后台工作线程 | 增量 | 12 KB (gzip) |
| react-markdown | 否(等待完整) | 不适用 | 基于插件 | 仅完整 | 15 KB |
| marked | 否 | 不适用 | 基于插件 | 仅完整 | 10 KB |
| 原生 innerHTML | 是,但不安全 | 渲染中断 | 手动 | 中断 | 0 KB(无依赖) |
| 自定义流式渲染器 | 部分 | 通常中断 | 手动 | 通常中断 | 视情况而定 |
使用示例
使用 Streamdown 与 React 和 Vercel AI SDK 非常简单:
import { Streamdown } from '@vercel/streamdown/react';
export function ChatMessage({ content }) {
return <Streamdown content={content} />;
}
该组件自动处理流式输入、渐进式渲染和最终解析。对于更高级的使用案例,Streamdown 提供用于自定义样式、主题集成和交互处理器的钩子。
开始使用
请访问 Streamdown GitHub 仓库 获取安装说明、API 文档和示例。该库以 @vercel/streamdown 形式在 npm 上提供,并支持所有主要前端框架。Vercel AI SDK 文档 提供将 Streamdown 与 AI SDK 流式响应结合的集成指南。
常见问题
什么是 Streamdown?
Streamdown 是 Vercel 的开源流式 Markdown 渲染器,可在 LLM 生成的文本逐个 Token 到达时显示,并对表格、代码块、列表和标题等 Markdown 元素进行渐进式渲染。
为什么流式 Markdown 渲染很困难?
标准的 Markdown 解析器假设输入是完整的。流式 Markdown 会逐步渲染 Token,因此渲染器必须处理部分语法——例如在表头之前到达的表格行,或未闭合的代码块——并逐步更新 DOM,而不会出现闪烁或布局偏移。
Streamdown 如何在流式期间处理代码语法高亮?
Streamdown 使用多遍渲染策略。代码块在 Token 到达时首先显示为无样式的纯文本,然后后台工作线程在块完成后应用语法高亮。这确保了零感知延迟,同时最终显示完全高亮的代码。
我可以在没有 React 的情况下使用 Streamdown 吗?
可以。Streamdown 与框架无关,并导出一个适用于任何前端栈的纯 JavaScript API。还有针对 React、Vue、Svelte 和 Solid.js 的专用集成,提供流式友好的钩子和组件。
Streamdown 与标准 React Markdown 有何不同?
标准的 React Markdown 库(如 react-markdown)需要在渲染之前提供完整的 Markdown 输入。Streamdown 专为增量渲染设计:它逐个 Token 更新实时 DOM,优雅地处理部分语法,并在流式完成时解析为正确渲染的 Markdown。
延伸阅读
- Streamdown GitHub 仓库 – 源代码、API 文档和示例
- Vercel AI SDK 文档 – 将 Streamdown 与 AI SDK 流集成
- Vercel 博客:AI 应用中的流式 – Vercel 对 AI 流式用户体验的观点
- CommonMark 规范 – Streamdown 实现的 Markdown 标准
無程式碼也能輕鬆打造專業LINE官方帳號!一鍵導入模板,讓AI助你行銷加分!