博客 / 詳情

返回

週末項目 Incremark:為 AI 流式輸出場景設計的增量 Markdown 解析器

故事的開始

故事的開始是我想要為我的 tiptap 編輯器增加 AI 流式內容輸出的功能,但 AI 輸出的是 markdown,我需要將其解析為 prosemirror JSONContent,但我又想盡可能節省性能,每次已經穩定的內容避免重複解析,正在生成的塊不斷進行更新,因此有了 incremark 這個小工具。

問題分析

傳統的 Markdown 解析器(marked、remark 等)設計用於處理完整文檔。但在流式輸出場景中,每次收到新內容都需要重新解析全部文本:

收到 chunk 1: 解析 "# Hello"
收到 chunk 2: 解析 "# Hello\n\nWorld"
收到 chunk 3: 解析 "# Hello\n\nWorld ..."
...

這導致了 O(n²) 的時間複雜度。文檔越長,問題越嚴重。

解決方案

Incremark 的核心思想是增量解析

  1. 識別已完成的 Markdown 塊(標題、段落、代碼塊等)
  2. 將已完成的塊"鎖定",不再重新解析
  3. 只解析新增內容和未完成的塊

這樣將複雜度從 O(n²) 降到 O(n)。

性能測試

為了驗證效果,我寫了一個 Benchmark,模擬不同長度文檔的流式輸入:

文檔大小 加速比 適用場景
~1KB 2-3x 短回覆
~5KB 9-11x 中等回覆
~10KB 17-23x 長回覆
~20KB 37-46x 超長回覆

説明

  • 加速比與 chunk 大小有關,chunk 越小(如 10 字符),加速比越高
  • 真實 AI 輸出通常是小 chunk(幾個到幾十個字符),接近測試中的高加速比場景
  • 短文檔場景下加速比不明顯,這是正常的(O(n²) 在 n 小時與 O(n) 差距不大)

測試環境:Node.js 20+,可以通過 pnpm benchmark 自行驗證。

技術挑戰

增量解析的核心難點是塊邊界檢測:如何判斷一個 Markdown 塊已經完成?

比如代碼塊:

```javascript
function hello() {

這時候還不能認為代碼塊完成,因為沒有閉合的 \`\`\`。

Incremark 針對不同塊類型實現了邊界檢測邏輯:

  • 代碼塊:檢測配對的 \`\`\` 或 ~~~
  • 列表:檢測縮進變化和列表模式中斷
  • 引用:檢測 > 前綴的連續性
  • 表格:檢測表格行模式
  • 段落:遇到空行或塊級元素時完成

使用方式

提供 Vue 和 React 官方集成:

# Vue
pnpm add @incremark/core @incremark/vue

# React
pnpm add @incremark/core @incremark/react
<script setup>
import { useIncremark, Incremark } from '@incremark/vue'

const { blocks, append, finalize } = useIncremark()

async function handleStream(stream) {
  for await (const chunk of stream) {
    append(chunk)
  }
  finalize()
}
</script>

<template>
  <Incremark :blocks="blocks" />
</template>

侷限性

坦誠地説,Incremark 也有一些侷限:

  1. 額外的邊界檢測開銷:增量解析需要做塊邊界檢測,這本身有一定開銷。對於極短的文檔(幾百字符),可能收益不明顯。
  2. 複雜嵌套結構:某些複雜的嵌套場景(如列表中的多層引用)邊界檢測可能不夠精確,會導致部分塊被重新解析。
  3. 不是萬能的:如果你的場景是一次性渲染完整文檔,傳統解析器可能更簡單直接。

適用場景

  • ✅ AI 聊天應用的流式輸出
  • ✅ 實時 Markdown 預覽
  • ✅ 流式文檔生成
  • ⚠️ 短文檔場景收益有限
  • ❌ 一次性渲染完整文檔(直接用 marked/remark 即可)

相關鏈接

  • 文檔:incremark-docs.vercel.app
  • Vue Demo:incremark-vue.vercel.app
  • React Demo:incremark-react.vercel.app
  • GitHub:github.com/kingshuaishuai/incremark

如果你也在開發 AI 相關應用並遇到類似問題,歡迎試用。有問題或建議可以提 Issue。

user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.