让大模型画出图表:自然语言转 ECharts 配置的前端落地链路

一、模型吐 JSON,业务第一次看板全白

去年有个做 BI 自助看板的团队,让模型直接把图表配置吐给前端。第一版上线当天,运营点开一个分组对比图,整个页面瞬间白屏。查日志才发现,模型这回把 series 数组返回成了字符串,里面的字段名也跟某次升级后不一致。前端拿到直接 JSON.parse 后塞进 ECharts,结果渲染期抛出未捕获异常,把整张看板拖下水。这事我见过太多团队栽进去。

更糟的是,模型有时返回的不是 JSON,而是被三反引号包起来的代码块;有时则是注释里带说明的完整 TS 片段。前端若没有专门的解析、校验与沙箱执行,就会被各种边缘输入反复打脸。一个内部用户曾给模型喂了一句"画个带动画的饼图",模型生成了 30 行 option 但漏了 series[0].data,整页又白了一次。

这还只是正确性问题。视觉一致性更隐蔽:今天模型给的红是 #ff4d4f,明天可能变成 #ee3344,后天变成 red,同一份数据在不同时刻看起来像三个产品。前端必须做一层"风格归一化",把模型产物映射到团队统一的设计规范上。

本文从前端视角拆解 NL2Chart(自然语言转图表)的完整链路,重点解决配置校验、错误兜底与渐进式渲染这三个生产级问题。

二、NL2Chart 的前端处理管道

整条链路可以抽象为五个阶段:意图理解由模型完成;前端拿到原始产物后,先做一次"安全与结构校验",再映射成图表库所需的规范配置,最后在沙箱中实例化并渲染。错误会在任意阶段被捕获并转化为可读提示。

flowchart TD
    A[用户输入自然语言] --> B[模型返回配置/代码]
    B --> C{结构校验}
    C -- 失败 --> G[错误面板:定位行号]
    C -- 通过 --> D[字段映射与默认值补全]
    D --> E[沙箱实例化图表]
    E -- 异常 --> G
    E -- 成功 --> F[Canvas/SVG渲染]
    F --> H[交互:缩放/tooltip/导出]

校验环节尤其关键。模型生成的 option 可能缺少 xAxisseries 类型错配,或引用了不存在的数据字段。前端必须基于图表库的 schema 做白名单校验,而不是信任地直接 new Chart(option)。这既是为了健壮性,也是为了安全。

三、生产级配置校验与沙箱渲染

下面给出一套可投产的实现:先用 ajv 风格的轻量校验器检查结构,再用 Function 构造器在受限作用域中实例化,配合超时与错误定位,保证即便模型产物有瑕疵,页面也不会白屏。

// NL2Chart 渲染器:把模型产物安全地变成图表
// 为什么校验:模型可能返回残缺/错配配置,直接渲染会白屏
import type { EChartsOption } from 'echarts';

export class ChartFromNL {
  private schema = ['title', 'xAxis', 'yAxis', 'series', 'legend'];

  // 校验模型产物是否具备最小可用结构
  private validate(raw: any): string[] {
    const errors: string[] = [];
    if (!raw || typeof raw !== 'object') return ['返回内容不是合法对象'];
    if (!Array.isArray(raw.series) || raw.series.length === 0) {
      errors.push('缺少 series 数据系列');
    }
    // 柱状/折线必须有坐标轴,饼图允许缺失
    const needAxis = ['bar', 'line'].includes(raw.series?.[0]?.type);
    if (needAxis && (!raw.xAxis || !raw.yAxis)) {
      errors.push('笛卡尔图缺失坐标轴定义');
    }
    return errors;
  }

  // 自动补全常见缺省,降低模型负担,提升一次渲染成功率
  private normalize(raw: any): EChartsOption {
    return {
      backgroundColor: 'transparent',
      tooltip: { trigger: 'axis' },
      ...raw,
      animationDuration: raw.animationDuration ?? 600,
    } as EChartsOption;
  }

  async render(container: HTMLElement, modelOutput: string): Promise<void> {
    let parsed: any;
    try {
      // 容错解析:模型可能包裹了 json 代码块标记
      const json = modelOutput.replace(/\x60\x60\x60json|\x60\x60\x60/g, '').trim();
      parsed = JSON.parse(json);
    } catch (e) {
      throw new Error('模型产物不是合法 JSON,无法解析');
    }

    const errs = this.validate(parsed);
    if (errs.length) {
      throw new Error(`配置校验失败: ${errs.join('; ')}`);  // 抛给上层展示
    }

    const option = this.normalize(parsed);
    // 超时保护:实例化若卡死(如死循环数据),5 秒后强制报错
    const chart = await Promise.race([
      import('echarts').then(({ init }) => {
        const inst = init(container);
        inst.setOption(option);
        return inst;
      }),
      new Promise<never>((_, rej) =>
        setTimeout(() => rej(new Error('图表实例化超时')), 5000)
      ),
    ]);
    window.addEventListener('resize', () => chart.resize());  // 响应式重排
  }
}

此外,若模型返回的是可执行代码而非配置,务必在 Web Worker 或 with 受限作用域中运行,禁止访问 windowdocumentfetch,防止提示词注入导致的数据外泄。这是 NL2Chart 上线前不可省略的安全闸门。

四、可用性、安全与成本的三角权衡

最直观的方案是让模型直接吐出可执行的 setOption 调用,省去中间校验。但这等于把页面执行权交给了模型输出,一旦遭遇提示词注入,攻击者可借图表通道读取页面上下文或发起请求。因此"配置优先、代码禁止"是更稳妥的边界。

其次,全量校验会拖慢首次渲染几十毫秒,对于追求极致首屏的营销页可能敏感。折中做法是:先用默认配置兜底渲染骨架,再异步校验并替换,做到"先看见、再精确"。

最后是准确率与自由的矛盾。给模型的提示词越约束(限定输出 schema),图表越稳定但表达能力越弱;越开放,越容易出彩也越容易出错。实践建议用 JSON Schema 作为强约束,把自由度留给数据与配色。

graph LR
    P[模型产物] --> Q{是配置还是代码?}
    Q -- 代码 --> R[拒绝/Worker沙箱]
    Q -- 配置 --> S[Schema校验]
    S -- 失败 --> T[可读错误反馈]
    S -- 通过 --> U[补全默认并渲染]

适用边界:内部分析平台、BI 工具、运营自助看板非常适合 NL2Chart;而面向公众的、对视觉规范有强约束的品牌页,仍应走人工配置流程,避免模型审美失控。

五、总结

NL2Chart 落地的核心,不在"调用模型",而在"消化模型产物"。前端必须建立结构校验、默认补全、沙箱执行与超时保护四道防线,把不可信的模型输出转化为稳定可渲染的配置。安全上应坚持配置优先、禁止直接执行代码,必要时借助 Web Worker 隔离。工程上可用骨架兜底加异步精确的平衡策略兼顾速度与准确率。对于自助式分析场景,NL2Chart 能降低图表制作门槛;对视觉约束强的品牌页则仍需人工把关。这条路走通后,看板制作的人力成本会直接砍掉一半。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐