ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

基于ECharts和EdgeOne打造云上智能图表:TaoToken统一Key接入实战

基于ECharts和EdgeOne打造云上智能图表:TaoToken统一Key接入实战 1. 边缘函数里跑 ECharts卡点其实不在图表本身ECharts 本身是纯前端库把echarts.min.js往页面一挂就能画图这件事没什么门槛。真正让人头疼的是「云上智能图表」这五个字里的「智能」——你想让用户用一句自然语言描述需求边缘函数接住请求调用大模型把这句话翻译成 ECharts 的option配置再返回给浏览器渲染。问题就出在这一步模型调用需要 Key而边缘函数是分布在全球节点上跑的你不可能把一把明文 Key 硬编码进函数代码里也不该在每个节点上各配一套环境变量。我试过最原始的做法把 Key 写进 EdgeOne 边缘函数的常量里本地调试没问题一上线就发现两个坑一是 Key 轮换时要重新发布整个函数二是不同项目共用同一把 Key额度混在一起根本没法排查是谁在烧。后来换成 TaoToken 的统一 Key 通道边缘函数只认一个base_url和一个api_key模型切换、额度查看、调用日志都在一个地方管函数代码里不再出现任何厂商相关的硬编码。这篇就按「EdgeOne 边缘函数 ECharts 静态页 TaoToken 统一 Key」这条链路把可复制的config.toml、settings.json、CC Switch 配置片段和边缘函数验证动作全部给出来。适合已经在用 ECharts 做可视化、想把 AI 生成图表配置这一步搬到云上的前端和全栈同学。读完你能拿到一套能直接跑通的骨架而不是又一篇讲 ECharts 有多少种图表的科普。2. 前置准备TaoToken 统一 Key 与 EdgeOne 边缘函数的关系先把架构讲清楚不然后面配置会晕。整个链路是这样的浏览器加载 ECharts 静态资源并渲染图表用户在输入框里写一句「把这份销售数据画成平滑折线图带面积填充」前端把这个 prompt 和数据结构 POST 给 EdgeOne 边缘函数边缘函数拿到请求后用 TaoToken 的统一 Key 去调用大模型让模型输出一段合法的 EChartsoptionJSON函数把这段 JSON 回给前端前端setOption渲染。TaoToken 在这里扮演的是「统一出口」的角色。官网在 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 入口是 https://taotoken.net/api 注意 API 地址不带 UTM 参数配置里填干净的就行。它的价值在于你不需要在边缘函数里区分「这次调的是哪个模型」只需要把模型名当参数传进去Key 始终是同一把。对于边缘函数这种部署分散、更新成本高的场景少一个变量就少一类线上事故。你需要提前准备的东西一个 EdgeOne 账号并开通边缘函数能力、一个已经能访问的静态站点放 ECharts 页面、一把 TaoToken 的 API Key。Key 在控制台的 API Keys 页面创建地址是 https://taotoken.net/console/api-keys 创建后复制出来后面config.toml和settings.json都要用。如果你还没决定用哪个模型可以先去模型对话页面 https://taotoken.net/chat 试几句确认模型能稳定输出 JSON 格式的图表配置再写进边缘函数。注意边缘函数的运行环境通常是受限的 JavaScript 运行时不是完整 Node.js。所以后面所有代码都避免使用fs、child_process这类 Node 专有模块只用fetch和标准 Web API。3. 可复制配置config.toml、settings.json 与 CC Switch 片段这一节是全文的核心三个配置文件分别对应「边缘函数运行时配置」「本地开发环境配置」「多模型切换配置」。你可以直接抄改掉 Key 和域名即可。3.1 config.toml边缘函数运行时骨架EdgeOne 边缘函数支持用配置文件声明环境变量和路由。下面这份config.toml放在项目根目录声明了 TaoToken 的 API 地址、Key 的引用方式以及函数入口。# config.toml - EdgeOne 边缘函数运行时配置 name echarts-ai-chart main src/edge/index.js compatibility_date 2024-11-01 [vars] TAOTOKEN_BASE_URL https://taotoken.net/api TAOTOKEN_MODEL claude-sonnet-4-5 CHART_OPTION_MAX_TOKENS 2048 # 敏感信息不要写死在 vars 里用 secret 注入 # 在 EdgeOne 控制台或 CLI 执行 # edgeone secret put TAOTOKEN_API_KEY [secrets] TAOTOKEN_API_KEY TAOTOKEN_API_KEY [[routes]] pattern /api/generate-chart methods [POST]这里有几个点值得展开。TAOTOKEN_BASE_URL填的是https://taotoken.net/api不带任何查询参数这是 API 的规范入口。TAOTOKEN_MODEL我默认写了claude-sonnet-4-5因为它在生成结构化 JSON 上比较稳你可以换成任何 TaoToken 支持的模型名。CHART_OPTION_MAX_TOKENS限制模型输出长度防止它话痨把整个响应撑爆——ECharts 的option通常几百 token 就够2048 是留了余量。[secrets]这一段是关键。Key 绝对不能出现在[vars]里因为vars会随代码一起进版本库。正确做法是在 EdgeOne 控制台或 CLI 里用 secret 命令注入函数运行时通过环境变量读取。这样即使代码仓库公开Key 也不会泄露。3.2 settings.json本地开发与 CC Switch 共用配置本地调试边缘函数时你不可能每次都部署到线上试。所以需要一份settings.json让本地 Node 环境也能跑同一套逻辑。这份配置同时被 CC Switch 读取用来在多个模型通道之间切换。{ provider: taotoken, baseUrl: https://taotoken.net/api, apiKeyEnv: TAOTOKEN_API_KEY, defaultModel: claude-sonnet-4-5, models: { chart-fast: { model: claude-haiku-4-5, maxTokens: 1024, description: 轻量图表配置生成响应快 }, chart-quality: { model: claude-sonnet-4-5, maxTokens: 2048, description: 复杂图表与多系列配置 } }, requestDefaults: { temperature: 0.2, responseFormat: json } }temperature设成 0.2 是有意的。生成 ECharts 配置这件事你要的是稳定和可解析不是创意。温度高了模型容易在 JSON 里加注释、加省略号前端JSON.parse直接报错。responseFormat设成json是提示模型尽量输出纯 JSON但别完全依赖它后面边缘函数里还是要做容错解析。3.3 CC Switch 配置片段一键切换模型通道CC Switch 是一个多通道切换工具配置片段如下。它的作用是让你在「快速模式」和「质量模式」之间切换而不用改代码。{ switches: [ { name: chart-fast, provider: taotoken, baseUrl: https://taotoken.net/api, model: claude-haiku-4-5, envKey: TAOTOKEN_API_KEY }, { name: chart-quality, provider: taotoken, baseUrl: https://taotoken.net/api, model: claude-sonnet-4-5, envKey: TAOTOKEN_API_KEY } ], active: chart-quality }把这段合并进你现有的 CC Switch 配置里active字段决定当前用哪个。本地开发时切到chart-fast省额度上线前切到chart-quality验证效果。两个通道共用同一把TAOTOKEN_API_KEY这就是统一 Key 的好处——切换模型不需要换 Key也不需要重新申请额度。4. 边缘函数调用验证从 prompt 到 ECharts option配置写完了得验证它真的能跑。这一节给出边缘函数的完整实现和验证动作。4.1 边缘函数主体代码// src/edge/index.js - EdgeOne 边缘函数入口 export default { async fetch(request, env) { if (request.method ! POST) { return new Response(Method Not Allowed, { status: 405 }); } let payload; try { payload await request.json(); } catch (e) { return jsonResponse({ error: 请求体不是合法 JSON }, 400); } const { prompt, dataSample, chartHint } payload; if (!prompt) { return jsonResponse({ error: 缺少 prompt 字段 }, 400); } const systemPrompt [ 你是一个 ECharts 配置生成器。, 只输出合法的 ECharts option JSON不要输出任何解释、注释或 Markdown 代码块标记。, 如果用户描述不完整选择最合理的图表类型。, 输出的 JSON 必须能被 JSON.parse 直接解析。 ].join(\n); const userPrompt [ 需求${prompt}, chartHint ? 图表类型提示${chartHint} : , dataSample ? 数据样例${JSON.stringify(dataSample)} : ].filter(Boolean).join(\n); const apiUrl ${env.TAOTOKEN_BASE_URL}/v1/messages; const body { model: env.TAOTOKEN_MODEL, max_tokens: parseInt(env.CHART_OPTION_MAX_TOKENS || 2048, 10), temperature: 0.2, system: systemPrompt, messages: [{ role: user, content: userPrompt }] }; let upstream; try { upstream await fetch(apiUrl, { method: POST, headers: { Content-Type: application/json, x-api-key: env.TAOTOKEN_API_KEY, anthropic-version: 2023-06-01 }, body: JSON.stringify(body) }); } catch (e) { return jsonResponse({ error: 上游请求失败, detail: String(e) }, 502); } if (!upstream.ok) { const errText await upstream.text(); return jsonResponse({ error: 模型调用失败, status: upstream.status, detail: errText }, 502); } const result await upstream.json(); const rawText extractText(result); const option safeParseOption(rawText); if (!option) { return jsonResponse({ error: 模型输出无法解析为 JSON, raw: rawText }, 422); } return jsonResponse({ option, model: env.TAOTOKEN_MODEL }); } }; function extractText(result) { if (result.content Array.isArray(result.content)) { return result.content.map((c) c.text || ).join(); } return ; } function safeParseOption(text) { if (!text) return null; let cleaned text.trim(); // 去掉可能的代码块围栏 cleaned cleaned.replace(/^(?:json)?/i, ).replace(/$/, ).trim(); try { return JSON.parse(cleaned); } catch (e) { // 尝试截取第一个 { 到最后一个 } const start cleaned.indexOf({); const end cleaned.lastIndexOf(}); if (start ! -1 end start) { try { return JSON.parse(cleaned.slice(start, end 1)); } catch (e2) { return null; } } return null; } } function jsonResponse(obj, status 200) { return new Response(JSON.stringify(obj), { status, headers: { Content-Type: application/json; charsetutf-8 } }); }这段代码里有三个细节是踩过坑才加上的。第一safeParseOption做了两层容错先剥代码块围栏再截取首尾大括号。模型有时候会输出「好的这是配置」然后跟一段 JSON直接 parse 必挂。第二extractText兼容了content数组结构因为不同模型的响应格式略有差异。第三错误响应里带上raw字段方便你在前端看到模型到底输出了什么而不是只看到一个「解析失败」。4.2 前端调用与渲染前端部分很轻核心就是拿到option后setOption。// src/js/aiHelper.js - 前端调用边缘函数 async function generateChartOption(prompt, dataSample, chartHint) { const resp await fetch(/api/generate-chart, { method: POST, headers: { Content-Type: application/json }, body: JSON.stringify({ prompt, dataSample, chartHint }) }); const result await resp.json(); if (!resp.ok) { console.error(生成失败, result); throw new Error(result.error || 未知错误); } return result.option; } // 使用示例 const chart echarts.init(document.getElementById(chart)); const option await generateChartOption( 把这份月度销售数据画成平滑折线图显示面积填充带数据标签, [{ month: 1月, sales: 120 }, { month: 2月, sales: 180 }], line ); chart.setOption(option);4.3 验证动作用 curl 直接打边缘函数部署后先用 curl 验证边缘函数本身通不通别急着开浏览器。curl -X POST https://your-domain.com/api/generate-chart \ -H Content-Type: application/json \ -d { prompt: 把下面的数据画成柱状图按销量降序排列, dataSample: [ {product: A, sales: 320}, {product: B, sales: 210}, {product: C, sales: 450} ], chartHint: bar }预期返回类似{ option: { xAxis: { type: category, data: [C, A, B] }, yAxis: { type: value }, series: [{ type: bar, data: [450, 320, 210] }] }, model: claude-sonnet-4-5 }拿到这个响应说明「边缘函数 → TaoToken → 模型 → 返回 option」这条链路是通的。如果返回 422看raw字段里模型输出了什么如果返回 502看detail里上游的状态码和错误信息。5. 本篇常见错排查这一节按报错现象来组织都是我在实际部署时遇到过的。现象一边缘函数返回 502detail 里是 401。这是 Key 没注入成功。检查config.toml里[secrets]的写法以及是否真的执行了 secret 注入命令。本地调试时确认TAOTOKEN_API_KEY环境变量已 export。注意 Key 不要带多余空格复制时容易带上换行。现象二返回 422raw 里模型输出了一段带解释的文字。这是模型没遵守「只输出 JSON」的指令。两个办法一是把temperature再降到 0.1二是在 system prompt 里加一句「如果无法生成输出空对象 {}」。另外确认responseFormat是否真的被上游支持有些模型对格式参数不敏感主要靠 prompt 约束。现象三前端JSON.parse报错但边缘函数返回 200。说明边缘函数返回的option里可能有非法字符比如模型输出了单引号而不是双引号。在safeParseOption里加一层替换cleaned.replace(//g, )要谨慎用因为字符串值里的单引号会被误伤。更稳的做法是让模型输出时就用双引号在 system prompt 里强调。现象四EdgeOne 边缘函数超时。模型调用本身有延迟如果边缘函数的超时设置太短比如 3 秒复杂图表配置生成会超时。在config.toml里检查是否有超时相关配置或者在控制台把函数超时调到 15 秒以上。同时前端要加 loading 状态别让用户以为页面卡死。现象五本地能跑线上报fetch is not defined。说明你的边缘函数运行时不是标准 Web 环境或者代码被打包工具处理过。确认compatibility_date设置正确且没有引入 Node 专有 polyfill。EdgeOne 边缘函数用的是标准fetch不需要额外引入。现象六CC Switch 切换后模型没变。检查active字段是否真的改了以及边缘函数读的是env.TAOTOKEN_MODEL还是配置文件里的值。如果模型名是写死在函数代码里的CC Switch 改了也没用。正确做法是模型名从环境变量读CC Switch 改的是环境变量对应的配置。6. 把 Key 管好把图表跑通整条链路跑下来最值得花时间的其实不是 ECharts 配置本身而是 Key 的管理方式。边缘函数部署分散、更新成本高一旦 Key 硬编码进去后面每次轮换都是一次全量发布。用 TaoToken 的统一 Key 通道函数里只留一个base_url和一个从环境变量读的 Key模型切换、额度查看、调用排查都在控制台完成函数代码保持干净。如果你后面要把这套东西用到长期编码或者 Agent 场景比如让模型根据数据库 schema 自动生成图表配置并持续迭代可以了解下 Coding Plan地址是 https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewrite 。接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 里面有各语言 SDK 的调用示例边缘函数里用fetch直接打也行用 SDK 封装也行。最后给一个实用建议在边缘函数里加一个简单的内存缓存对相同的prompt dataSample哈希做短时缓存比如 60 秒。图表配置生成是幂等的同一个需求反复请求没必要每次都调模型。边缘函数实例在短时间内是复用的一个模块级的Map就能挡住不少重复调用。这个优化不复杂但能明显降低额度和延迟。
返回列表