资讯动态

AI内容导出乱?用解析-改写-渲染架构治理格式与公式问题

发布时间:2026/10/3 7:32:04 来源:尧图企业网站定制
这半年来我干得最多的一件事就是帮团队里的人把AI生成的内容从对话框里搬运到正式文档。搬运本身倒不麻烦真正让人头疼的是搬运完的那一刹那列表层级丢了、代码块的高亮没了、公式变成一行纯文本乱码、表格在Word里摊成一片废墟。明明在AI窗口里看着整整齐齐的内容一导出就残废。我被这种AI内容导出乱、格式崩、公式变的问题折磨了无数个晚上之后干脆动手写了个内部小工具代号就叫 DuckIt图标是只橡皮鸭。一开始纯粹是自嘲——老一辈程序员遇到解决不了的Bug会把橡皮鸭放在桌上对着它一行行讲代码。我遇到的Bug虽然不是程序逻辑问题但性质差不多AI写出来的内容本身质量不错只要别让我碰格式它写得比很多初级编辑都好。可一旦涉及导出、转换、排版它立刻从高级助手变成格式粉碎机。这篇文章就围绕鸭子的开发过程把AI内容导出这个问题掰开揉碎讲清楚。我会先分析格式崩、公式坏、内容乱这三类问题的根因再说我为什么选择解析-改写-渲染三段式架构然后把攻防记录和实测数据列出来最后聊聊这只鸭子还能往哪儿走。如果你也经常被AI生成的Markdown、Word文档、公式转换折磨这篇文章应该能给你一些可以直接用的思路。1. 先搞清楚三个痛点到底痛在哪格式崩、公式坏、内容乱的根因很多人遇到AI内容导出问题第一反应是换个转换工具再试一次。我一开始也是这样pandoc换到PandocTypora换到Obsidian导出插件装了七八个结果该崩的还是崩。后来我意识到一个问题不是工具不够好是我们把问题归因搞错了。AI生成内容的格式问题从来不是转换器不够强而是AI平台输出的格式本身就带病。1.1 格式崩不是AI不行是Markdown从来就没有统一标准Markdown最大的优点是简单最大的缺点也是简单。它没有一个像HTML那样的W3C标准组织来维护统一规范于是出现了CommonMark、GitHub Flavored Markdown、Pandoc Markdown等一系列方言。AI平台在训练时吸收了各种风格的语料输出时又会根据用户的提示词自由发挥这就导致同一个AI在不同对话里可能输出完全不同的Markdown结构。我实测过同一个模型生成的同一份技术方案第一次输出用圆点列表嵌套方括号第二次输出用数字列表加四个空格缩进第三次干脆混着来外圈是数字列表内圈是圆点列表。单独看任何一次输出在对话窗口里渲染都正常可是一旦丢给pandoc转Word问题就全出来了。Terrible 的地方在于解析器会强制按标准规则解释比如pandoc遇到列表缩进不一致时不会自动猜你的意图而是直接把列表打断成好几段。表格是另一个重灾区。AI输出Markdown表格时经常出现列数不一致的情况第一行表头写了6列数据行里有的写了5格有的写了7格。这在GitHub上渲染时会被宽容处理看起来只是错位但一旦导成Word或者HTML表格就会彻底崩坏。还有更隐蔽的情况——AI喜欢在表格单元格里塞Markdown语法比如加粗、行内代码、甚至一个完整的代码块。按规范这不算错但大部分表格解析器根本不支持单元格内有块级元素转换时直接丢弃内容。我统计过自己经手的200多份AI生成文档格式崩里最常见的三类是表格列数不一致、列表嵌套缩进混乱、代码块内部出现未转义的反引号或三个反引号。这三类问题几乎每个星期都能遇到。1.2 公式变LaTeX、MathML、UnicodeMath三套方言互相打架公式问题比格式问题更让人头大。原因很简单公式在AI对话窗口里显示得好是因为前端做了渲染而一旦导出后端需要把公式翻译成目标格式能识别的语言这里就有三套完全不同的体系在打架。第一套是LaTeX。这是AI输出公式最常见的格式ChatGPT、Claude这些模型默认就给LaTeX用$...$或者$$...$$包裹。问题在于LaTeX本身是排版语言不是数据格式同一个数学表达式可以用完全不同的LaTeX命令表达。比如\frac{a}{b}和a\over b甚至a/b在LaTeX里都能表示分数但不同转换器对它们的支持程度不一样。第二套是MathML。它是为了在网页里表示数学而生的XML语言结构规整信息完整但极度冗长。一个简单的分数在MathML里要写十几行标签。很多导出流程会把LaTeX先转换成MathML再由MathML转换成其他格式这个链路越长出错概率越高。第三套是Word原生公式使用的格式。Word在2010年之后主要支持UnicodeMath也称为线性格式以及OMMLOffice Math Markup Language。问题在于从LaTeX到UnicodeMath的转换并不是一一对应的很多结构在LaTeX里有表达方式在UnicodeMath里根本没有等价物或者表达方式完全不同。举个例子分段函数里常用的\begin{cases} ... \end{cases}结构在UnicodeMath里对应的是⌈这样的专用括号加矩阵排列绝大多数转换器直接放弃处理。还有多行对齐的\begin{aligned}、矩阵的\begin{matrix}、求和符号上下限的\sum_{i0}^{n}这些在LaTeX里稀松平常的结构一旦要转成Word公式十有八九会变成一坨纯文本或者乱码。更麻烦的是同一平台在不同场景下的公式存储方式还不一样。Markdown导出时是LaTeX通过浏览器复制粘贴时可能变成MathML直接复制渲染结果时又变成UnicodeMath。格式不统一转换工具就很难做兼容。1.3 内容乱AI爱用没有语义的Markdown完成格式化表演格式崩和公式变是显性问题内容乱是隐性问题但它带来的后果往往更严重。什么是内容乱不是说文字排列歪了而是指Markdown标记被AI当成格式化表演来用失去了真正的语义。很多AI生成的文档看起来层次分明但它的层次只是视觉上的——该是标题的地方用了加粗该是列表的地方用了缩进加横线该是正文的地方用了引用块。这些内容在对话窗口里渲染看起来像一份结构清晰的文档但一旦丢给解析器提取大纲、生成目录、做书签立刻露馅。我遇到过几个典型场景。有一次让AI帮忙写一份季度汇报它为了突出三个重点写了三个引用块每个引用块里再套列表这样从视觉上确实很清晰。可是我要把这份文档转成带目录的Word投屏生成的目录只有汇报两个字三个重点全是灰色正文。还有一次AI把整个流程说明写在了表格里但过程有十二步表格只有两列结果就是内容全堆在一个单元格里完全没法编辑。我自己也反思过这个问题AI为什么会这样因为模型训练时接触了大量格式优美的网页内容它学会了怎么让文字看起来有条理但没有学会怎么让文档在语义上结构正确。加粗、缩进、引用块这些视觉元素对AI来说可能只是看起来更专业的手段。但对和文档处理工具打交道的我们来说这些都是误导信号。所以鸭子的核心目标从一开始就确定了不只是把马克down变成Word而是把视觉上有条理的内容变成语义上结构正确的内容。这个定位决定了后面的整个架构设计。2. 鸭子工具的整体设计我为什么选择解析-改写-渲染三段式架构确定要自己动手之后我第一件事不是写代码而是花了两天时间把手头的AI内容和各种转换器的输出全部过了一遍。结果让我下了决心不能用现成的转换器硬拼必须做一个能治病的中间层。2.1 为什么不能靠市面上现成的转换器市面上的转换器分两类。一类是从A格式到B格式的直接转换代表是pandoc另一类是渲染并导出的富文本编辑器或笔记软件代表是Typora、Notion、语雀。这两类工具在自己的生态内都做得不错但用来处理AI内容都有一个通病一条流水线从源头直接到目标中间没有语义检查和结构修复的机会。pandoc是我最开始用的也是用得最多的。它的转换质量确实高尤其在标准文档上。但pandoc默认把输入内容当作已经在语义上正确的内容处理它不会去判断一个表格列数是否一致不会去修复嵌套列表的缩进更不会去纠正AI把强调写成标题的问题。换句话说pandoc是个优秀的翻译官但不是个负责任的编辑——原文有病句翻译出来也是病句。Typora这类编辑器又不一样。它们通常在渲染层做了一些宽容处理比如表格列数不一致时自动补空栏但宽容带来的问题是你根本不知道原始文本哪里有问题。等你导出成Word才暴露这时候又要回头改原始Markdown工作效率极低。更重要的是AI平台输出格式还在快速变化。上个季度ChatGPT的列表缩进还是标准四空格这个季度可能就变成两空格有些模型开始用HTML标签包裹列表有些模型习惯在代码块外再加独立行内代码。任何依赖固定输入格式的转换工具都注定要被时代甩在后面。所以我当时的判断是要处理AI内容导出问题不能靠转换器要靠解析器修复器渲染器。也就是先理解AI到底输出了什么再动手改最后才做转换。2.2 三段式架构到底怎么拆鸭子的第一版架构很简单三个模块职责分明。解析层负责把不同来源的内容统一成抽象语法树AST。无论是ChatGPT直接复制来的Markdown、Claude导出的纯文本、还是从Notion里带格式复制出来的富文本解析层都先把它变成一棵结构化的树。这一步的关键是宽容——尽量保留原始信息即使语法不规范也不要丢把能识别的节点都标记出来识别不了的放进fuzzy节点等后面处理。改写层负责做结构修复。这是鸭子和普通转换工具最大的区别。改写层拿到解析层输出的AST之后不是立刻渲染而是先做一轮结构检查表格列数不一致就补列列表缩进混乱就按语义重排标题层级跳跃就插入占位节点公式内容无法映射就保留原文并标注警告。这一层相当于给AI输出做了一次结构化体检。渲染层负责目标格式输出。同一棵修复过的AST可以根据需要渲染成Word的docx、标准Markdown、带样式的HTML、PDF甚至直接输出成纯文本。渲染层不关心内容的对错它只负责把AST节点映射成目标格式的语法。因为AST已经修好了渲染时基本不会遇到结构性问题。为了方便理解我贴一段鸭子早期版本的类型定义不长但能直观看到这个架构的轮廓// 鸭子工具早期版本的核心AST类型定义 interface DuckNode { type: string; // 节点类型heading, paragraph, list, table, codeBlock, mathBlock... children?: DuckNode[]; props: Recordstring, unknown; // 结构属性level, ordered, align... source?: string; // 原始文本片段用于debug和回退 warnings?: string[]; // 改写层的修复记录 } interface DuckDocument { meta: { title: string; createdAt: string; model?: string }; nodes: DuckNode[]; }实际开发中改写层的核心是一组按节点类型注册的管道处理器。表格问题有表格处理器列表问题有列表处理器公式问题有公式转换器。这样设计的好处是每种问题都可以单独修、单独测。比如后来我发现Claude输出的表格单元格里偶尔会出现br标签我只需要在表格处理器里加一条规则不需要动其他代码。2.3 为什么鸭子能扛住真实场景说了这么多架构上的理由可能有人觉得我在过度设计。我真实的心路历程是这样第一版鸭子我偷懒了直接用正则表达式做了个修复脚本目标是最快最省事。正则方案在最开始的十几次转换里表现还行可到了第二十次就崩了——遇到一个AI在表格里嵌套代码块的极端案例我的正则匹配直接错位把整个文档的结构搞乱了。那次翻车让我下了决心必须用解析树而不是正则以字符串匹配。用AST之后处理逻辑变成了树的遍历和节点操作每一步都可以打印、检查、单测。真实世界里的AI输出千奇百怪但无论怎么变形只要它还能被解析成树就有机会被修复。就像做菜不用管食材本身是什么形状先切块切完才开始烹饪。切块的过程就是把原料变成可处理的状态。而且三段式架构还有一个隐形好处可测试性。我可以给每个环节写独立的测试用例。比如解析层需要保证无论输入多乱的Markdown都不抛异常改写层需要保证表格列数不一致时自动补列不丢失单元格内容渲染层需要保证同一棵AST生成Word和生成HTML的内容完全一致。这些测试帮我挡住了很多回归问题AI平台一升级我先跑一遍测试就知道哪里受影响了。3. 三场硬仗的攻防记录乱、崩、公式怎么逐个击破架构定下来之后剩下的就是硬仗了。我按痛点的优先级排了三个攻坚方向先解决格式崩因为它是出现频率最高的再解决公式变因为它最影响专业文档的质量最后处理内容乱因为它需要更精细的语义判断。3.1 第一仗搞定格式崩从表格和代码块下手表格的问题我选择用独立解析器处理。所谓独立解析器就是在解析层不去猜表格哪一列是哪一列而是严格按Markdown表格语法解析把每一行拆成单元格数组然后把列数是否一致的判断留给改写层。改写层里有一条规则当同一表格内不同行的列数不一致时以表头为准执行数组对齐缺失的格子补空字符串多出来的格子内容拼接到最后一个单元格后面并记录一条警告。这个策略在理论上很简单但实操中我踩了一个很重要的坑AI生成表格时单元格里可能出现竖线|字符比如代码片段Math.abs(x) | 0。在标准Markdown里这需要用\|转义但很多AI不转义。如果我按竖线硬切单元格内容就散架了。后来我在解析表格时加了个状态机只在非代码块、非转义的竖线处切分这才把问题压下去。代码块的问题主要集中在嵌套反引号上。AI非常喜欢在代码块里贴另一段包含反引号的代码合法的Markdown可以用双反引号包裹外层但我见过AI直接输出三个反引号包一个包含三个反引号的代码片段这在任何解析器里都是灾难。我的处理方式是解析层先把识别到的最外层代码块包围标记记下来然后在改写层做一次代码块内容安全检查——如果代码内容里出现了和包围标记相同的连续反引号就自动提升包围层级。这一步看起来简单但背后的逻辑是不要试图告诉AI该怎么写而是把AI写坏的内容修复成解析器能理解的形式。因为无论AI怎么升级总会有边界情况修复逻辑必须比AI输出更宽容。3.2 第二仗搞定公式变从LaTeX到Word原生公式的最短路程公式转换是整只鸭子投入精力最多的地方没有之一。一开始我也想直接找现成的LaTeX转OMML库试了两三个开源方案发现效果都一般简单公式没问题但一遇到\begin{cases}、\begin{bmatrix}、\begin{aligned}这种环境块要么报错要么输出成满屏的OMML标签Word里根本没法编辑。后来我放弃了一步到位的思路改成三层处理。第一层是LaTeX解析层。我不直接转OMML而是先把LaTeX解析成一个数学表达式树。这里要重点处理的是环境块——\begin{cases}、\begin{matrix}等在标准LaTeX里属于环境它们不仅有符号含义还有布局语义。我把它解析成带布局类型的数学节点比如cases节点、矩阵节点、多行对齐节点。第二层是符号映射层。这一层负责把数学表达式树里的LaTeX命令映射成UnicodeMath能理解的符号。比如\alpha映射成α\sum_{i0}^{n}映射成带下标和上标的∑结构\frac{a}{b}映射成a/b的线性表示同时给渲染器标注这是一个分数节点需要用分数布局渲染。第三层才是OMML拼接层。我基于内嵌的公式引擎直接构建OMML XML节点核心是m:oMath元素内部的各种结构节点。分数用m:f上下标用m:sSubSup矩阵用m:m分段函数用m:d加分组。这一层最笨但效果最可控因为每个节点都是我亲手映射的不会像第三方库那样尽力而为。这里我特别想强调一个细节公式的双写机制。Word文档里OMML是给Word渲染用的但一旦用户用别的工具打开或者复制粘贴到纯文本环境OMML就变成了满屏乱码。为了让公式可读可编辑我在生成Word时会把原始LaTeX同时写入公式节点的alt文本。这样就算一个公式在某个环境下渲染失败用户至少还能凭LaTeX原文找回内容不会彻底丢失。这个双写机制我强烈推荐给所有做文档转换的人。它不能提高转换成功率但能大幅降低转换失败带来的损失。3.3 第三仗搞定内容乱用AST做语义级整理搞定格式和公式之后我花了好几周处理内容乱的问题因为它的判断标准不像前两个那么明确。什么叫结构正确不同场景有不同答案。我采取的判断标准是以文档目标格式的语义模型为准。如果目标是Word文档那么标题必须是标题样式列表必须是列表样式强调必须用字符级属性而不是用看起来像标题的加粗段落。这个标准说起来简单但执行起来需要一系列规则。以标题为例。AI经常把章节标题写成实心加粗大号字体这种视觉样式在AST里它就是一个加粗段落但实际语义是标题。改写层里加了一个标题嗅探规则当一个段落同时满足全文独占一行内容长度小于80个字符带加粗属性前面有数字序号或特定标志词时就自动把它提升为标题节点。这个规则有误判风险我做了个折中设计只升不降并且在警告列表里记录已按疑似标题处理让用户可以在最终预览时手动退回正文。列表的整理也有类似问题。AI经常输出1. 第一步 - 打开设置 - 找到开关这样的混排列表。解析层会把这些拆成不同层级的列表项但缩进可能完全不对。改写层用了一个简单的栈式算法重建层级遇到列表项时根据它的上一级列表项的缩进和序号状态决定是同一层级还是降一级还是升一级。这个算法不完美但对90%的AI列表内容能给出正确结果。还有一类乱我花了很多时间才意识到空行和分隔线。AI为了让文档透气会在段落之间插入大量水平分割线---在Word里会变成一条线。视觉上没问题但一旦文档被脚本处理这些分割线会干扰语义解析。我在改写层加了一个规则连续出现2条以上的分割线时只保留最后一条并记录警告。这条规则看起来很小但在实际导入知识库时帮了大忙。4. 实测复盘在真实工作流里鸭子能解决多少还剩哪些边界理论说得再漂亮最终得看实测。鸭子开发到现在已经跑了五个月我用它处理了400多份来自不同AI平台、不同场景的文档这里把数据和个人感受都摊开说。4.1 主流模型与常见平台的实测需要说明一点这不是严格控制的对比实验而是我在真实工作流里的观察记录。所谓成功率指转换完成后不需要人工手动清理整个段落的比例不包含个别标点、个别样式的小修小补。来源样本量格式崩修复率公式保留率表格修复率主要遗留问题ChatGPTGFM输出12096%92%90%列表缩进偶尔重排不彻底ClaudeMarkdown输出8591%88%85%表格内嵌HTML标签残留通义富文本复制7084%79%80%复制时丢失空格和换行信息文心纯文本回复5589%无公式场景84%转换前需自动补Markdown标记本地开源模型4578%70%76%平台输出格式极不稳定需调解析层级几个明显发现。第一ChatGPT的Markdown输出在结构上最规整说明底层做了比较严格的规范约束。第二Claude喜欢在列表和表格里塞HTML标签增加了解析负担但处理掉标签后内容质量依然很高。第三通义这种富文本复制模式的难点在于信息丢失——你从对话框复制内容到剪贴板时换行和空格已经被压缩了解析层很难做到无损还原。公式保留率是我比较担心的数字。92%的公式保留率不等于92%的公式转OMML成功有些公式是原样保留成LaTeX文本了。真正能做到转成Word原生公式且可编辑的比例在ChatGPT场景里大约85%。剩下的15%基本都是极端环境块比如\begin{rcases}或者自定义宏包命令这些只能保留LaTeX原文并给出警告。4.2 途中踩过的额外坑除了三大核心问题还有几个看似不起眼实际能坑人一整天的细节值得单独记一笔。Unicode字符的编码坑。AI输出里经常有各种奇怪的Unicode数学字符比如≠、∈、∫。这些字符在Word里显示没问题但在转HTML或PDF时如果目标编码不支持就会变成问号方块。鸭子里的处理方式是在所有渲染层的入口统一做一次字符规范化把Unicode数学字母平面Mathematical Alphanumeric Symbols映射到普通字母加斜体标记。图片和链接的引用问题。AI生成的Markdown里经常夹带![alt](url)但URL可能是临时链接、过期链接甚至base64数据。鸭子的渲染层默认开启图片下载并本地化选项遇到base64数据直接解码写入docx内的embed遇到临时链接尝试下载下载失败就放占位图并在警告列表里提示。超长文本的分段问题。有一次我让AI写了一份上万字的操作手册导出时Word直接卡死。后来发现是渲染层构建docx时把所有内容塞进了一个大段落没有任何分页和分节逻辑。我花了一个周末给渲染层加了智能分段——按标题层级自动生成分页按列表和段落实现分节这才把大文档的处理速度提上来。4.3 提高输出质量的个人建议测试做多了我发现一个规律鸭子能修复的质量上限取决于AI原始输出的信息完整度。如果AI输出的内容本身就缺了半个字或者把一个语法完整的句子自言自语截断了任何工具都没法完美复原。所以我的实际工作流做了调整在提示词端就把导出友好这件事交代给AI。具体做法是在提示词里加一句请使用标准Markdown格式输出标题层级从H2开始不要使用嵌套引用块确保表格行内不用竖线符号。这句提示词看起来轻描淡写实际效果却非常明显格式问题至少减少了六成。双保险的做法也强烈推荐永远保留AI的原始输出。鸭子只负责再加工不覆盖原始来源。这是因为AI对话窗口本身就带重新生成能力如果鸭子转换失败你可以让AI换个输出风格再试一次而不是抱着同一份坏数据反复修补。这是处理不可控内容最基本的防御策略。5. 这只鸭子接下来还能做什么从文档修补到内容工程鸭子目前解决的是AI内容导出后乱七八糟的问题但它本质上做的是一件更通用的事将不可控内容标准化。基于这一点我能看到的扩展方向还挺多的。第一是批量处理能力。单个文档转换跑通之后批量转换是水到渠成的事。我已经给鸭子写了简单的事件队列模块支持把一个目录下的所有Markdown文件批量转成Word并生成一个汇总的修复报告。这个能力对于需要把几十份AI日报、周报统一归档的团队非常有用每次转完还能自动发一份哪些地方被自动修复过的警告清单给负责人。第二是与知识库系统的对接。很多团队现在把AI生成的技术文档直接丢进内部知识库比如语雀、飞书云文档、Confluence。这些系统都有导入接口但接口接收的格式不同对格式的容忍度也不一样。鸭子输出的标准化Markdown和docx天然适合作为导入前的预处理器。我下一步计划是给鸭子加上输出适配器让它直接生成不同知识库平台期望的格式省掉先转Word再粘进系统这一步。第三是CI/CD集成。对于技术团队AI生成的代码注释、API文档、变更记录经常要进Git仓库。鸭子可以设计成一个命令行工具或Git插件在提交之前自动检查Markdown格式规范、修复表格和公式问题、统一标题层级。这相当于给AI生成的内容加一道格式CI检查在文档进入正式仓库之前就把病句和乱格式拦下来。回到我自己最常用的场景。我每周至少用鸭子处理十几份AI辅助写成的技术文档从初稿整理、格式修复到最终交付整个链路顺畅了很多。以前一个下午都搞不定的AI内容整理成Word文档现在大约一两分钟。真正让我觉得钱和精力没白花的是那些AI生成得很好但格式乱成一团的内容现在丢给鸭子就能恢复原本该有的模样。如果让我给同样被这个问题困扰的人提一条建议我想说别急着无脑修一个个具体案例先把AI输出→AST→修复→渲染这条链路搭起来。格式崩、公式变、内容乱这三个问题表面看着是三个独立问题根上都是同一个病缺少一个可靠的结构化中间层。把中间层做稳了后面全是顺的。还有一句个人体会。做这种工具最快乐的事不是技术环节有多高级而是每次亲眼看到AI输出被修得整整齐齐的那一刻会有一种终于把不可控的事情变成可控了的踏实感。鸭子到现在也不完美我也一样但至少我们都能在工作流里面站得更稳一点了。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑