AI 生成图表时,最容易出问题的部分往往不在数据读取,而在图表配置。字段要按日期解析,连续数值要使用合适的比例尺,分类轴需要控制标签密度,正负值要匹配发散色阶,分面布局还要处理画布尺寸。把这些决定全部交给模型,生成结果容易出现配置冗长、布局溢出和修改困难。
Flint 是 Microsoft Research 发布的可视化中间语言。它让 AI agent 或开发者提交一份紧凑的图表规格,规格里描述数据、字段语义、图表类型和视觉编码,编译器负责推导解析规则、比例尺、坐标轴、聚合、格式化、颜色方案与布局,随后输出 Vega-Lite、ECharts、Chart.js、Plotly 或 Excel 原生图表所需的配置。
这套设计的阅读门槛较低:人可以直接编辑 JSON,模型也能稳定生成;渲染库的差异被封装在后端 assembler 中。Flint 项目站点目前展示了 50 种图表类型和 121 个示例,GitHub 仓库采用 MIT License。

先理解 Flint 的输入
Flint 的输入对象有三个主要部分:data、semantic_types 和 chart_spec。
data 可以内嵌行数据,也可以引用本地 JSON、CSV 或 TSV 文件。内嵌数据的形态是 { values: [...] },每一行是一个对象。semantic_types 为字段绑定语义类型,例如 Category、Quantity、Price、Country、Rank、Temperature 和 YearMonth。chart_spec 描述图表类型、编码通道、目标尺寸和模板属性。
下面是一份可直接交给 flint-chart 的输入:
import { assembleVegaLite } from 'flint-chart';
const input = {
data: {
values: [
{ quarter: 'Q1', revenue: 1200 },
{ quarter: 'Q2', revenue: 1450 },
{ quarter: 'Q3', revenue: 980 },
{ quarter: 'Q4', revenue: 1800 },
],
},
semantic_types: {
quarter: 'Quarter',
revenue: 'Price',
},
chart_spec: {
chartType: 'Bar Chart',
encodings: {
x: { field: 'quarter' },
y: { field: 'revenue' },
},
baseSize: { width: 480, height: 320 },
},
};
const vegaLiteSpec = assembleVegaLite(input);编码通道可以使用 x、y、color、size、shape、column、row、group 和 detail。通道值既可以写成字段名,也可以写成带有 field、type、aggregate、sortOrder、sortBy 和 scheme 的对象。
semantic_types 与字段名分离,是 Flint 的关键取舍。字段名只能提供弱提示,语义类型则能明确告诉编译器:某个字段代表金额、日期、排名还是类别。类型信息会影响编码类型、格式化方式、聚合默认值、比例尺和颜色分类。
编译器具体做了什么
官方开发文档把图表组装划分为三个阶段。
第一阶段是语义解析。Flint 读取字段的语义类型和数据值,决定日期解析、坐标轴类型、数字格式、颜色策略以及部分聚合行为。例如,YearMonth 字段会进入时间处理流程,Profit 这样的数值语义可以触发以零为中心的发散色阶。
第二阶段是布局计算。Flint 的 baseSize 表示图表希望达到的目标尺寸,密集数据可以让画布适度伸展;canvasSize 则是硬上限,图表不能超过它。布局模型会根据离散轴上的项目数量调整带宽、间距、标签和分面子图尺寸。字体也会随着画布和子图尺寸变化,减少标签重叠。
第三阶段是后端实例化。共享的输入对象会被交给不同的 assembler:assembleVegaLite 返回 Vega-Lite JSON,assembleECharts 返回 ECharts option,assembleChartjs 返回 Chart.js 配置,assemblePlotly 返回 { data, layout },assembleExcel 返回可序列化的 Excel 图表工件。

这意味着应用层可以保留同一份“图表意图”,只在渲染阶段选择后端:
import {
assembleVegaLite,
assembleECharts,
assembleChartjs,
assemblePlotly,
assembleExcel,
} from 'flint-chart';
const vegaSpec = assembleVegaLite(input);
const echartsOption = assembleECharts(input);
const chartjsConfig = assembleChartjs(input);
const plotlyFigure = assemblePlotly(input);
const excelArtifact = assembleExcel(input);我在 Node 22 环境中安装 [email protected],用官方 API reference 中的季度收入示例分别调用了这五个 assembler。五个调用都返回了目标后端对象,结果没有附带 warning。这个验证也说明 Flint 的核心 API 是统一输入、后端独立输出,切换渲染库不需要重写数据和编码结构。
语义类型解决了什么问题
直接生成 Vega-Lite 或 ECharts 配置时,模型需要同时处理数据语义和渲染细节。比如一张净新增用户热力图,低层配置至少要交代以下内容:时间字段如何解析,横轴怎样显示月份,热力图单元格如何确定宽度,纵轴类别是否排序,正负值使用什么颜色范围,颜色中点放在哪里。
Flint 的输入只需表达这些意图:
{
"semantic_types": {
"game": "Category",
"period": "YearMonth",
"newUsers": "Profit"
},
"chart_spec": {
"chartType": "Heatmap",
"encodings": {
"x": "period",
"y": "game",
"color": "newUsers"
},
"chartProperties": {
"colorScheme": "redblue"
}
}
}编译器再把这份高层描述展开为后端需要的 mark、encoding、scale、domain、height.step 等字段。对于需要反复改图的工作流,这种结构有两个实际收益:数据与语义类型可以复用,探索过程只需替换 chart_spec;人也能在 JSON 层面检查和修正模型输出,不必在一大段后端配置中寻找问题。
布局自动化的边界
自动布局不意味着所有图表都能无条件塞进固定画布。Flint 会计算离散通道的容量,按照模板策略保留数据,并把截断或布局信息放进 warning。官方 API reference 给出的默认策略包括:折线和面积图优先保留全部点;用户指定排序时保留排序靠前或靠后的项目;定量轴会按数值排序;柱状图配合计数时可以先聚合再截断。
应用集成时,不能只读取渲染结果,还要把 _warnings 或 ChartWarning 数组展示给用户。假设一张排行榜默认只容纳 20 个类别,编译器为了保持可读性保留了其中一部分,界面应该明确告诉用户发生了截断,并提供排序、筛选或增大画布的入口。
baseSize 和 canvasSize 也需要分开理解。前者是舒适的目标尺寸,后者是布局上限。报表卡片、固定宽度的嵌入区域和导出图片更适合设置 canvasSize;数据探索页面可以只设置 baseSize,让密集数据获得一定伸展空间。
接入 AI agent:MCP 服务器
Flint 同时提供 flint-chart-mcp。它把图表编译能力包装成五个 MCP 工具:
render_chart:编译并返回 PNG 或 SVG。Chart.js 后端只支持 PNG。compile_chart:返回后端原生规格、warning 和计算后的尺寸。validate_chart:只做合法性检查,返回错误、warning 和尺寸。list_chart_types:查询某个后端支持的图表类型及编码通道。create_chart_view:在支持 MCP App UI 的客户端中打开交互式图表视图。
MCP 服务器的渲染在本地进程完成。数据可以通过 data.values 内嵌,也可以通过 data.url 读取本地 JSON、CSV 或 TSV;远程 URL 不会被服务器抓取。默认配置允许读取 agent 所在主机的本地文件,面向不受信任的部署时可以加上 --disable-file-reference,强制所有数据以内嵌行的方式传入。
最小客户端配置如下:
{
"mcpServers": {
"flint": {
"command": "npx",
"args": ["-y", "flint-chart-mcp"]
}
}
}MCP 服务器专门提供 Vega-Lite、ECharts 和 Chart.js 三个渲染后端。Plotly 与 Excel 仍然可以通过 JavaScript 库直接调用。create_chart_view 会在客户端中渲染实时 SVG,并提供图表类型、通道绑定、排序和图表属性的调整入口,用户可以把修改后的规格复制回对话。

这套设计适合“提问、生成、检查、修改”的短循环。模型先生成 ChartAssemblyInput,调用 validate_chart 检查结构,再使用 create_chart_view 让用户观察结果。需要静态图片时再调用 render_chart。对于生产报表,仍然应该把数据权限、字段白名单和结果审核放在 MCP 之外,由宿主应用控制。
研究结果如何解读
Microsoft Research 的介绍文章给出了 Flint 与 DirectVL 的一组对比。实验使用 Tidy Tuesdays 数据,并在 LLM self-evaluation 流程中测试三个模型。Flint 的整体 LLM judge 分数如下:
| 测试模型 | Flint | DirectVL |
|---|---|---|
| GPT-5.1 | 16.27 | 15.91 |
| GPT-5-mini | 16.16 | 15.60 |
| GPT-4.1 | 15.91 | 15.34 |
这组结果说明语义层减少了模型需要直接操纵的低层参数,模型在该测试设置下获得了更高评价。它不能替代真实业务中的可读性评审、数据准确性检查和用户研究,也不能说明 Flint 对每一种数据集或每一种图表都占优。更稳妥的用法是把它视为一层编译基础设施:负责把可读的图表意图转为后端配置,把数据权限、指标定义和业务判断留给应用层。
Flint 已被用于 Microsoft Research 的 Data Formulator 项目。它的价值也正在这里:图表规格具备共享语义,模型可以生成,人可以编辑,渲染后端可以替换,应用还可以在 warning 和尺寸信息的基础上决定如何呈现结果。
适合哪些项目
Flint 适合以下工作流:
- AI agent 根据自然语言和结构化数据生成探索性图表。
- 同一份数据需要在网页、服务端导出和 Excel 中使用不同渲染后端。
- 团队希望让分析师直接编辑规格,同时保留统一的语义字段定义。
- 图表类型、排序、颜色和布局属性需要被 agent 工具显式发现和验证。
它也有明确边界。JavaScript/TypeScript 是当前主要使用路径,Python 端口仍是仓库中的源码预览。图表模板和语义类型覆盖面决定了编译效果,复杂的专业可视化仍需要查看具体后端支持情况。自动布局产生的 warning 不能被静默丢弃,业务应用需要为截断、聚合和尺寸变化设计可见反馈。
如果项目已有 Vega-Lite 或 ECharts 配置,也不必一次性重写全部图表。可以先把一类探索性报表接入 Flint,让 semantic_types 成为字段语义层,再按后端输出逐步替换原有配置。这个迁移路径的工程价值在于,数据模型和图表意图可以独立维护,渲染库更换时影响范围更小。
结语
Flint 解决的是 AI 生成可视化中的一个具体工程问题:让模型描述图表意图,让编译器处理大量容易出错的渲染细节。它没有把图表判断交给一段不可检查的黑盒输出,而是把输入规格、语义类型、布局计算、后端组装和 warning 都保留在可观察的链路中。
对于需要 AI 图表、跨后端渲染或人机协作编辑的项目,Flint 值得作为一层中间表示来评估。先从一个固定数据集和一个后端开始,验证语义类型、布局 warning 和人工修改体验,再决定是否接入 MCP 及更多后端。