Skip to main content

Documentation Index

Fetch the complete documentation index at: https://nvd-54.mintlify.app/llms.txt

Use this file to discover all available pages before exploring further.

大语言模型(LLM)天然会产生 Markdown 格式的文本,包括标题、列表、代码块、表格和内联格式。将这些内容渲染为纯文本会浪费模型提供的结构。本模式展示了如何在智能体流式输出过程中实时解析和渲染 Markdown,涵盖所有主要前端框架。

Markdown 渲染的工作原理

渲染管道分为三个步骤:
  1. 接收:useStream 将流式文本累积到每条 AI 消息的 msg.text 中,随着新 Token 到达进行响应式更新。
  2. **解析:**Markdown 解析器将原始文本转换为 HTML(或 React 元素树)。每次更新都会运行,但对于聊天长度的内容足够快(5 KB 消息 < 5ms)。
  3. **渲染:**将解析后的输出渲染到 DOM。React 使用虚拟 DOM diff;Vue 和 Svelte 使用经过清理的 HTML 配合 v-html / {@html}

设置 useStream

Markdown 模式使用一个简单的聊天智能体,无需特殊配置。用你的智能体 URL 和助手 ID 连接 useStream 定义一个与你的智能体状态 schema 匹配的 TypeScript 接口,并将其作为类型参数传递给 useStream,以获得类型安全的状态值访问。在以下示例中,将 typeof myAgent 替换为你的接口名称:
import type { BaseMessage } from "@langchain/core/messages";

interface AgentState {
  messages: BaseMessage[];
}
import { useStream } from "@langchain/react";
import { AIMessage, HumanMessage } from "@langchain/core/messages";

const AGENT_URL = "http://localhost:2024";

export function Chat() {
  const stream = useStream<typeof myAgent>({
    apiUrl: AGENT_URL,
    assistantId: "simple_agent",
  });

  return (
    <div>
      {stream.messages.map((msg) => {
        if (AIMessage.isInstance(msg)) {
          return <Markdown key={msg.id}>{msg.text}</Markdown>;
        }
        if (HumanMessage.isInstance(msg)) {
          return <p key={msg.id}>{msg.text}</p>;
        }
      })}
    </div>
  );
}

选择 Markdown 库

每个框架都有自然的 Markdown 渲染选择:
框架输出原因
Reactreact-markdown + remark-gfmReact 元素基于组件,虚拟 DOM diff,无 dangerouslySetInnerHTML
Vuemarked + dompurify通过 v-html 清理过的 HTML轻量、快速,内置 GFM 支持
Sveltemarked + dompurify通过 {@html} 清理过的 HTML与 Vue 相同,API 一致
Angularmarked + dompurify通过 [innerHTML] 清理过的 HTML与 Vue/Svelte 相同
React 的 react-markdown 直接将 Markdown 转换为 React 元素,因此不需要 HTML 清理。没有涉及 dangerouslySetInnerHTML。对于 Vue、Svelte 和 Angular,在渲染前始终使用 dompurify 清理解析后的 HTML。

构建 Markdown 组件

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

export function Markdown({ children }: { children: string }) {
  return (
    <div className="markdown-content">
      <ReactMarkdown remarkPlugins={[remarkGfm]}>
        {children}
      </ReactMarkdown>
    </div>
  );
}

清理 HTML 输出

当将解析后的 Markdown 渲染为原始 HTML(v-html{@html}[innerHTML])时,你必须清理输出以防止跨站脚本攻击(XSS)。LLM 的响应可能包含任意文本,包括 Markdown 解析器可能转换为可执行 HTML 的标记。 使用 dompurify 去除危险元素:
import DOMPurify from "dompurify";

const safeHtml = DOMPurify.sanitize(rawHtml);
DOMPurify 移除 <script> 标签、onclick 属性、javascript: URL 和其他 XSS 向量,同时保留安全的 Markdown 输出,如标题、列表、代码块、表格和链接。
React 的 react-markdown 不需要 dompurify,因为它直接生成 React 元素,不涉及原始 HTML 注入。

流式输出注意事项

useStream 随着每个 Token 到达响应式地更新 msg.text。Markdown 组件在每次更新时重新解析。对于典型的聊天消息,这是高效的:
  • marked 以约 1 MB/s 的速度解析。5 KB 消息耗时 < 5ms
  • react-markdown + remark 管道对于聊天长度的内容同样快速
  • 浏览器的布局引擎高效地处理 DOM 更新
对于非常长的响应(> 50 KB),考虑以下优化:
  • **节流渲染:**使用 requestAnimationFrame 以 60fps 批量更新,而不是每个 Token 都重新渲染
  • **增量解析:**只解析新内容并追加到渲染缓冲区(高级,通常聊天 UI 不需要)
对于大多数聊天应用,在每个 Token 上重新解析完整消息的简单方法就足够了。只有在非常长的消息中观察到滚动卡顿或丢帧时才需优化。

设置 Markdown 内容样式

.markdown-content 类应用样式来控制渲染 Markdown 的外观。以下是基本样式:
.markdown-content p {
  margin: 0.4em 0;
}

.markdown-content ul,
.markdown-content ol {
  margin: 0.4em 0;
  padding-left: 1.4em;
}

.markdown-content pre {
  overflow-x: auto;
  border-radius: 0.375rem;
  background: rgba(0, 0, 0, 0.05);
  padding: 0.5rem;
  font-size: 0.75rem;
}

.markdown-content code {
  border-radius: 0.25rem;
  background: rgba(0, 0, 0, 0.08);
  padding: 0.125rem 0.25rem;
  font-size: 0.75rem;
}

.markdown-content blockquote {
  margin: 0.4em 0;
  padding-left: 0.75em;
  border-left: 3px solid currentColor;
  opacity: 0.8;
}

.markdown-content table {
  border-collapse: collapse;
  margin: 0.4em 0;
}

.markdown-content th,
.markdown-content td {
  border: 1px solid #e5e7eb;
  padding: 0.25em 0.5em;
}
聊天气泡中的 Markdown 样式应保持紧凑。聊天消息比博客文章小,因此使用比典型散文样式表更紧凑的边距和更小的字号。

最佳实践

  • **始终清理:**使用 v-html{@html}[innerHTML] 时,始终对解析输出运行 dompurify。永远不要信任来自 LLM 输出的 Markdown 解析器生成的原始 HTML。
  • **启用 GFM:**GitHub 风格 Markdown 添加了表格、删除线、任务列表和自动链接。LLM 常用这些功能。
  • **处理空内容:**解析前检查空字符串,避免渲染空容器。
  • **使用 breaks: true:**启用换行转换,使 LLM 输出中的单个换行符渲染为 <br> 而非被忽略。LLM 经常使用单个换行符进行视觉分隔。
  • **为聊天场景设置样式:**使用适合聊天气泡的紧凑边距和大小,而非全宽文章布局。
  • **使用丰富内容测试:**验证标题、嵌套列表、长代码块、宽表格和引用块的渲染效果,以发现溢出或布局问题。