如何将Markdown解析算法为?

摘要:在先前我们我们实现了SSE流式输出的实现,以及基于向量检索的RAG服务,这些实现都可以算作是AI Infra的范畴。这里我们再来聊一下在SSE流式输出的基础上,将Markdown解析和富文本编辑器的渲染结合起来,实现编辑器的增量解析算法,同
在先前我们我们实现了SSE流式输出的实现,以及基于向量检索的RAG服务,这些实现都可以算作是AI Infra的范畴。这里我们再来聊一下在SSE流式输出的基础上,将Markdown解析和富文本编辑器的渲染结合起来,实现编辑器的增量解析算法,同样属于文档场景下的Infra建设。 概述 在SSE流式输出的场景下,LLMs模型会逐步输出Markdown文本,在基本场景下我们只需要实现DOM的渲染即可。然而,在富文本编辑器的场景下,这件事就变得复杂了起来,因为编辑器通常都是自行维护一套数据结构,并不可以直接接受DOM结构,再叠加性能问题就需要考虑到下面几点: 流式: 流式意味着我们需要处理不完整的Markdown文本,在不完整的情况下语法会出现问题,因此需要支持已渲染内容的重建。 增量: 增量意味着我们不需要每次都进行全量渲染,已经稳定的内容实际上不需要再次解析,而是只需要在已有内容的基础上进行增量更新。 富文本解析: 富文本解析意味着我们需要完整地对应到Md的解析情况,无论是流式处理还是增量解析都需要在编辑器结构基础上对等实现。 当然,即使是在基本场景下的DOM渲染,也会涉及到很多边界情况的处理,例如若是每次都是SSE输出Md时都进行全量渲染的性能问题、以及类似图片节点重渲染可能存在的重新加载问题等。因此,这里的增量解析算法流程,即使是对于基本场景也是很值得参考的。 并且,还有更复杂的场景,例如在SSE流式输出的过程中,用户总是会存在临时的不完整的Md文本,在完整输出Md文本之前,就会存在不符合规范的Md解析,或者是错误地匹配到另一种Md语法的情况。这些情况都需要额外的算法处理,实现一些额外的语法修复方案。 <img src="http:// <img src="http://example.com/ <img src="http://example.com/image.png" alt= 此外,诸如渲染的语法扩展、自定义组件等等,也会涉及到额外的语法处理,关于自定义解析渲染的实现可以参考react-markdown。这其实是是一个很庞大的管道实现,同样也会是很好的自定义处理AST并渲染的案例,remark也是非常丰富的生态系统,给予了关于Md解析的各种实现。 markdown -> remark + mdast -> remark plugins + mdast -> remark-rehype + hast -> rehype plugins + hast-> components + ->react elements 不过在这里我们并不展开讨论这些自定义语法解析的内容,而是主要聚焦在基本的Md语法解析和增量渲染上,但是在解析的过程中我们还是会涉及到针对语法错误匹配的相关问题处理。文中的相关实现可以参考 BlockKit 以及 StreamDelta 中。 %%{init: {"theme": "neutral" } }%% graph LR 文本 --> |片段| 词法解析 --> |Tokens| 游标指针 --> DC[Delta 变更] 编辑器 --> DF[Delta 片段] --> R[Retain 指针] --> D[Diff Delta] --> 应用 DC --> D Markdown增量解析 首先我们来实现Markdown的流式增量解析,能够实现增量解析的重要的基础是,Md输出的后续内容格式通常不会影响先前的格式,即我们可以归档已经稳定的内容。因此我们可以设计一套数据处理方案,在解析的过程中需要遵循的整体大原则: 非全量解析Markdown, 基于流渐进式分割结构处理数据。 基于Lexer解析的结构, 双指针绑定Delta的增量变更。 词法解析 首先我们需要一个词法解析器将Md解析成Token流,或者是需要一个语法解析器将Md解析成AST。由于我们的数据结构是扁平化的,标准词法解析的扁平Token流对我们的二次解析并非难事,而若是完全嵌套结构的数据结构,语法解析生成AST的解析方案对则可能更加方便。 当前的主流解析器有比较多的相关实现,marked提供了lexer方法以及marked.Lexer作为解析器,remark作为unified庞大生态系统的一部分,也提供了mdast-util-from-markdown独立的解析器,markdown-it同样也提供了parse方法以及parseInline作为解析器。 在这里我们选择了marked作为解析器,主要是由于marked比较简单且易于理解。
阅读全文