AI 流式 Markdown 单次解析预算:分段封口与语义门禁
按帧合并只能减少 commit 次数,不能降低一次 commit 的 Markdown 工作量。本文用封口块、延迟富化和确定性重放约束解析成本与最终语义。
本文要点
- 主题聚焦:AI、React、TypeScript
- 适合人群:独立开发者、全栈工程师、AI 应用开发者
- 核心价值:快速理解背景、实践路径、常见误区和工程建议
流式 Markdown 最容易出现隐蔽的平方级工作:第一个片段解析 100 字,第二个解析 200 字,直到反复重算整篇回答。LLM 流式前端实现 与 RAG 流式 UI 已介绍缓冲、稳定段和延迟富化,流式界面提交频率预算 则限制每秒 commit 数。本篇从完整记录已验证且某批文本已选入 commit 后开始,只约束这一次提交的扫描、解析、富化成本与最终 CommonMark 语义。
用相同正文和不同分块证明语义
基准正文固定 100 KiB,包含 30 个标题、20 个总计 600 行的代码围栏、3 张表格、80 个引用链接、中文组合字符和一个故意延迟闭合的代码围栏。把同一正文分别切成单字符、1 至 64 字节随机块、按行块和 4 KiB 块,以每秒 30 次及突发 200 次两种节奏重放。报告保存正文 SHA-256、分块种子、解析器与插件版本、React 19.2.4、浏览器构建和 CPU 降速倍率。
门禁要求:最终规范化 HTML 与无障碍文本摘要在所有分块方式下相同;字符无丢失或重复;单个主线程任务不得超过 50 毫秒;解析 slice P95 不超过 8 毫秒;Profiler 渲染子树的 actualDuration P95 不超过 12 毫秒;已接收字符到安全可见文本的 P95 不超过 150 毫秒;总解析字符量不超过最终正文的 2.5 倍。最后一项直接阻止每次从零解析,即使高速机器暂时没出现长任务。TypeScript 异步迭代 可以组织输入,但异步语法本身不会给事件循环让出足够预算。
尾段可变,封口块不可变
展示模型分为 sealedBlocks 和 openTail。解析器只在确定局部块边界后封口,例如段落空行、完整标题、闭合代码围栏或协议提供的语义边界;未闭合围栏和列表留在尾段。CommonMark 的 link reference definition 可以出现在引用之后,因此“封口后永不重解析”只适用于明确禁用这类全局语法的流式子集。若产品支持 reference link,解析器记录 label 到块的依赖,定义到达时只使相关块失效;结束时无论如何执行一次规范全量解析,并以它作为最终树。尾段先以转义纯文本或受限 Markdown 展示,等语法确定后再替换为完整节点。
不能简单按换行切字符串:围栏、列表、表格和内联标记会跨块。边界器必须是有状态的增量扫描器,并对富文本解析的最大未封口长度设置上限,例如 16 KiB;超限后保持纯文本,但扫描游标仍以常数状态继续前进。若 64 KiB 后才出现闭合围栏,结束阶段的规范解析仍须恢复代码块语义,不能永久把它当作普通文本,也不能为了找闭合符反复扫描整篇。来自网络的 framing 与结构解码继续遵循 浏览器流式结构化数据解析,这里的封口只决定视图语法,不承诺传输完整性。
稳定块首次渲染后才排队做富化:语法高亮、数学公式、复制按钮和链接预览各有独立任务 ID,结果绑定块摘要。块被替换或 run 取消时丢弃旧结果。富化每次处理一个有限块并在任务之间让出事件循环;若单块高亮仍可能超过 50 毫秒,就送到已有 Web Worker 并发 路径,主线程只应用匹配摘要的结果。Worker 不改变预算,只把可并行 CPU 移出交互线程。
useDeferredValue 可以让富化树落后于最新纯文本,却不会取消网络请求,也不会让同步解析自动进入后台。界面必须明确保留最新安全文本,deferred 版本只增强表现;不能因 deferred 树较旧而把复制内容或引用目标退回旧版本。代码高亮样式预留尺寸,避免替换节点时产生布局跳动。
逐阶段记录,而不是只看 FPS
每个片段记录 received,每个封口块记录 scan_done、safe_commit、enrich_start、enrich_done,通过 performance.measure 汇总。PerformanceObserver 同时收集 longtask;React profiling 构建记录目标子树的 actualDuration,浏览器 trace 负责 DOM、布局与绘制归因。测试在流期间持续输入、选择文字、滚动和展开引用,并在解析、提交、高亮三个位置注入 20 毫秒延迟,确认队列有界且紧急输入仍能绘制。
另建两个 64 KiB 代码围栏夹具:一个永不闭合,另一个在超过 16 KiB 后才闭合。两者在流中都保持有界纯文本尾段,扫描游标只前进不回退;前者结束时保留原文并标记格式未完成,后者在最终规范解析后必须与一次性 CommonMark 解析得到相同代码块。再让引用先封口、reference definition 后到,断言依赖块被失效且最终链接语义一致,不能补写符号来制造一棵看似合法的树。
上线采用双算单显:1% 流量继续展示旧渲染器,在采样会话后台生成新规范树摘要,比较最终结果和成本,不把两棵 DOM 同时挂到页面。摘要一致后再让新路径显示 1%,观察长任务、可见延迟、内存与复制结果。任一正文摘要不一致、单任务超过 50 毫秒比例上升、开放尾段超过上限或 P75 INP 增加 20%,开关回到“流中纯文本、完成后一次解析”的保守路径。回滚不重连流,也不丢已接收正文。
延伸阅读
Related