![]()
去年大模型应用井喷时,一个隐蔽的痛点被反复踩中:LLM生成的markdown再漂亮,一旦用户要图表、要表单、要可排序的数据表格,整个渲染层就开始崩。
缓冲整块JSON会杀死流式体验,逐token解析HTML又像在拆炸弹——标签没闭合怎么办?尖括号混在正文里怎么区分?
Stripe前工程师创建的Markdoc,原本只是文档框架里的冷门语法,现在被一群开发者挖出来,当成了LLM流式UI的解药。
1. 流式渲染的三重陷阱
做AI聊天界面的团队,几乎都在同一个地方摔过跤。
第一方案是JSON代码块。LLM把图表数据包在```json里输出,前端提取后渲染。流式场景下这方案直接报废:JSON对象逐token到达时,在闭合括号出现之前都是非法格式。你要么缓冲整块内容(用户看着空白等半天),要么硬着头皮解析残缺JSON(随时报错)。
第二方案是HTML/JSX。让模型直接输出这样的标签。但模型会搞混HTML属性和JSX props,会忘记闭合标签,会在普通文本里随意使用<字符——流式解析器根本分不清这是标签开头还是数学符号。
第三方案是自定义语法。比如[[chart:bar:Q1=120,Q2=150]]。代价是重新训练模型适应新格式,烧掉大量token写格式说明,还要自己维护解析器。
三种方案,三种妥协。
2. 为什么偏偏是Markdoc
Markdoc是Stripe 2022年开源的文档框架,核心创新是用[% %]包裹自定义标签,扩展标准markdown。
开发者Chris Garrett(mdocUI作者)发现这个冷门语法恰好解决流式渲染的三大痛点:
分隔符足够怪异。[%这个序列在正常文本、标准markdown、代码块里都几乎不会出现。流式解析器检测到它就能立即切换状态,不需要向前偷看或回溯。
模型已经学过。Stripe文档、Cloudflare文档都用了Markdoc,这些在LLM训练数据里。模型写这种语法不需要额外提示,准确率远高于自定义格式。
正文和组件无缝混排。不需要模式切换,LLM一边写markdown一边丢组件,解析器随token到达实时分离。
Chris的mdocUI只借用了Markdoc的标签语法,流式解析器是从头手写的。架构很直白:LLM token → 分词器 → 流式解析器 → 组件注册表 → 渲染器。
分词器是个字符级状态机,三个状态:IN_PROSE(正文)、IN_TAG(标签内)、IN_STRING(字符串内)。token到达时实时分离正文和组件。
3. 实际用起来长什么样
Chris放出的示例里,LLM输出这样的内容:
Here's your revenue data:
[% chart type="bar" labels=["Q1","Q2","Q3"] values=[120,150,180] /%]
Revenue grew 12% quarter-over-quarter.
[% button action="continue" label="Show by region" /%]
用户看到图表和按钮随着文字流实时出现,没有卡顿,没有闪烁,没有"正在加载"的占位符。
组件系统完全开放。你可以注册任意React组件,从简单按钮到复杂的数据表格、可视化图表、交互式表单。LLM只需要知道组件名和props,不需要理解渲染细节。
Chris提到一个细节:状态机设计让错误处理变得简单。如果标签语法写错了,解析器能精确定位错误位置,不会污染后续内容。这比JSON解析器遇到语法错误就整段丢弃要友好得多。
4. 谁已经在用
mdocUI目前还是个人项目,但思路已经被多个团队验证。
Vercel的AI SDK在2024年推出的streaming helpers,处理了类似的流式JSON解析问题,但走的是另一条路——要求LLM输出特定格式的工具调用,而不是扩展markdown。
Anthropic的Claude Artifacts功能,让模型生成可运行的React组件,但采用的是完整文件输出,不是流式增量渲染。
Stripe自己似乎没把Markdoc往这个方向推。他们的官方文档还在强调"为大规模文档站点设计",对AI场景保持沉默。
Chris在GitHub讨论区回复用户提问时说:「我试过让Claude直接输出JSX,结果10次里有3次会搞混单双引号,或者忘记转义特殊字符。Markdoc的语法约束更严格,模型犯错率明显更低。」
他补充了一个数据点:在GPT-4和Claude 3.5 Sonnet的对比测试中,两者写Markdoc标签的语法正确率都超过95%,而JSX的正确率大约在80%左右。
5. 局限和未解的问题
这套方案并非万能。
组件props目前只支持基本类型:字符串、数字、布尔值、数组。复杂对象需要序列化成字符串再解析,增加了LLM和前端双方的负担。
嵌套标签的语法还没定稿。Chris的草案里支持[% list %][% item /%][% /list %]这种结构,但流式解析的复杂度会指数级上升。
最大的问号是生态。Markdoc本身是Stripe的边缘项目,社区规模远小于MDX。如果mdocUI想做成通用方案,可能需要兼容MDX语法,或者推动新的社区标准。
Chris的回应很直接:「我现在优先解决自己的问题。如果更多人遇到同样的痛点,标准会自然浮现。」
他在文档最后埋了一个钩子:「想象一下,LLM输出一段分析文字,中间突然插入一个可交互的模拟器,让你调参数看结果——全程没有页面跳转,没有加载状态,就像文字本身会呼吸。」
这种体验,现在的AI产品里还很少见。是技术没ready,还是没人想到这么做?
特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.