【声明】本博客所有内容均为个人业余时间创作,所述技术案例均来自公开开源项目(如Github,Apache基金会),不涉及任何企业机密或未公开技术,如有侵权请联系删除
标题
218、【AI】【模型部署】Notebook 源码拆解:输出 data 的多格式与前端择优渲染
背景
上篇 blog
【AI】【模型部署】Notebook 源码拆解:.ipynb 文档到底长什么样
解剖了.ipynb:outputs 有 execute_result/display_data/stream 等类型;execute_result 与 display_data 的data是"按 mime 类型分键"的字典(IMimeBundle,index.ts:71-73);validateMimeValue规定"JSON 型 mime 只许对象、非 JSON 型许字符串/数组"(只一句带过);真实样例里 data 同时出现text/html、image/svg+xml、image/jpeg等 mime。217 到此为止——"为什么同一份结果要存多个 mime 版本、前端到底怎么挑、挑完怎么渲染"三个问题没展开。本篇补上:先讲清 mime 是什么,再用源码把"多格式 data → 择优 → 渲染"这条链拆开
模型部署
在 Notebook 里,一个display_data可以既带网页版又带纯文本版,文件里存的不是"一份输出",而是一组按格式分键的候选。为什么这么设计、代码怎么选的——从"mime 是什么"讲起。
💡mime 是什么:给数据贴"怎么解读"的标签
mime 是Multipurpose Internet Mail Extensions(多用途互联网邮件扩展)的缩写,起源于电子邮件——用它给附件/正文标格式;后来被 HTTP 借去当Content-Type,成为互联网通用约定。写法是"类别/子类":
| mime | 中文含义 | 在 Notebook 里出现于 |
|---|---|---|
text/plain | 纯文本 | 所有输出的兜底 |
text/html | 网页标记(可渲染富内容) | 富文本输出 |
text/markdown | 标记文本 | Markdown 渲染 |
image/png/image/jpeg | 位图 | 图片输出 |
image/svg+xml | 矢量图 | 矢量图形 |
application/pdf | 文档 | PDF 渲染 |
一句话:mime 是数据的"格式身份证",告诉接收方"这段内容该按什么格式解读"。
🔍同一份结果,为什么存多个格式
解剖 jupyterlab 自带的examples/notebooks/OutputExamples.ipynb(统计共16 个输出带 ≥2 个 mime),挑 cell 12 的execute_result,它的data长这样:
{"text/html":["<div style=\"width:100px;height:100px;background:red\"></div>"],"text/plain":["<IPython.core.display.HTML object>"]}两份值表达的是同一个 HTML 对象:text/html版是可渲染的网页片段(这里是一个红色方块 div),text/plain版是对象的普通字符串表示(<IPython.core.display.HTML object>)。设计意图是给"不会渲染 HTML"的接收端留退路——终端、老版本界面看到 plain 版也能知道"这里有个 HTML 对象"。这就是"多格式并存"存在的理由:富表达优先、兜底保底。
🧩data 的字段树与值类型约束
data在源码里是"mime 为键、值为内容"的字典(nbformatindex.ts:71-73):
exportinterfaceIMimeBundleextendsPartialJSONObject{[key:string]:MultilineString|PartialJSONObject;}值允许两种:**多行字符串(string | string[])**或JSON 对象。哪个 mime 允许哪种值,由validateMimeValue(nbformatindex.ts:101-140)裁决,核心逻辑可用一段伪码还原:
jsonTest = mime 形如 application/json 或 application/*+json? 若 值是数组 → 仅当 非 JSON 型 mime,且元素全是字符串,才合法 若 值是字符串 → 仅当 非 JSON 型 mime 若 值是对象 → 仅当 JSON 型 mime,且是合法 JSON 对象规则本质是防错位:JSON 型 mime 的数据就该是结构化的 JSON 对象,不能塞字符串;图片/文本类 mime 的值才允许是字符串或多行字符串。真实样例里的值都是"单元素数组"——["<div…>"]、["<IPython…>"],正是多行字符串的数组形态。
🧩前端侧一:每种 mime 挂一个"渲染工厂"
数据侧定了格式,前端侧要有能渲染每种格式的工人——rendermime/src/factories.ts注册了一批标准工厂,每种 mime 声明它服务哪些类型、安不安全、默认排第几(数值越小越优先):
| 渲染器 | mime | defaultRank | safe | 说明 |
|---|---|---|---|---|
| RenderedHTML | text/html | 50 | 是 | 富文本最高优先 |
| RenderedMarkdown | text/markdown | 60 | 是 | |
| RenderedLatex | text/latex | 70 | 是 | |
| RenderedSVG | image/svg+xml | 80 | 否 | SVG 可嵌脚本 |
| RenderedImage | image/png 等 | 90 | 是 | |
| RenderedText | text/plain 等 | 120 | 是 | 兜底 |
| RenderedJavaScript | text/javascript | 110 | 否 | 能执行代码 |
// factories.ts:11-15 的 HTML 工厂结构(示意关键字段){mimeTypes:['text/html'],defaultRank:50,safe:true,createRenderer:options=>newwidgets.RenderedHTML(options)}safe是关键标记:能跑脚本/嵌入外链的格式(HTML、SVG、JavaScript)被标记为不安全,渲染与否取决于文档是否受信任——后面择优逻辑会用到。
🧩前端侧二:择优算法 preferredMimeType
"挑哪个格式渲染"的核心在rendermime/src/registry.ts:105-129的preferredMimeType:它按排序后的 mime 列表从前到后找,谁的键在 data 里出现就返回谁。下面是该函数去掉注释后的主体(registry.ts:110-125):
if(safe==='ensure'||safe==='prefer'){for(constmtofthis.mimeTypes)if(mtinbundle&&this._factories[mt].safe)returnmt;// 第一轮只认安全}if(safe!=='ensure'){for(constmtofthis.mimeTypes)if(mtinbundle)returnmt;// 第二轮全放行}returnundefined;“排序后的列表"由sortedTypes(registry.ts:645-654)决定:rank 小的在前,rank 相同按注册先后;工厂注册时若没给 rank 默认取 100(registry.ts:225-237)。回看 cell 12 的{text/html, text/plain}:text/html rank=50、text/plain rank=120,html 排前面且 data 里有 → 返回text/html。整条判断就是一次"排好序的查表”。
🧩渲染主链与"信任"开关
择优结果在哪一步被用上?outputarea/src/widget.ts:785-800的createRenderedMimetype是主链:
// outputarea/src/widget.ts:786-794(略注释)constmimeType=this.rendermime.preferredMimeType(model.data,model.trusted?'any':'ensure'// 信不信任决定放行范围);if(!mimeType){returnnull;}// 一个都渲染不了 → 跳过letoutput=this.rendermime.createRenderer(mimeType);信任开关在此落地:cell 元数据里的trusted(217 提过的字段)为真时用'any'(html/svg/js 都能渲染),否则用'ensure'(只允许安全工厂)。拿 cell 29 的{image/svg+xml, text/plain}推演:svg rank=80 但在第一轮 safe 检查中被跳过,text/plain rank=120 反而胜出——于是未信任的 Notebook 里 SVG 图不会渲染、只显示文本;点"信任"后才轮到 svg 渲染。安全不是靠禁止,而是靠"信任前只给看安全版本"。
📊小结:一条 data 的一生
| 环节 | 位置 | 干了什么 |
|---|---|---|
| 数据侧 | nbformatIMimeBundle | 一份结果存成 {mime: 值} 字典 |
| 校验 | nbformatvalidateMimeValue | 保证值与 mime 类型匹配 |
| 候选 | rendermime 工厂表 | 每种 mime 绑一个渲染器 + rank + safe |
| 择优 | registrypreferredMimeType | 按 rank 排序,信任决定放行范围 |
| 渲染 | outputareacreateRenderedMimetype | 挑中 → createRenderer → 塞进 cell |
📌一句话记忆
mime(Multipurpose Internet Mail Extensions,多用途互联网邮件扩展)是数据的"格式身份证";Notebook 的 output data 是 {mime: 值} 字典,一份结果可存 HTML 富版 + plain 兜底版;前端每种 mime 挂一个带 rank 与 safe 标记的渲染工厂,
preferredMimeType按 rank 排序查表、trusted为真才放行 html/svg/js 等不安全渲染——多格式是为了"富表达 + 降级兜底",择优与信任开关保证安全。
OK,本篇先到这里,如有疑问,欢迎评论区留言讨论,祝各位功力大涨,技术更上一层楼!!!更多内容见下篇 blog
【AI】【模型部署】装库前先“隔离“:venv 虚拟环境从概念到落地