ARTICLE DETAIL

资讯详情

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

Dify工作流数据可视化:代码执行节点+ECharts完整实践

Dify工作流数据可视化:代码执行节点+ECharts完整实践 做知识库问答的都知道让大模型把结果念出来容易让它直接在聊天框里画出一张不辣眼睛的图特别难。你让大模型写 HTML它给你一坨带script的字符串前端又不敢直接执行怕 XSS。后来我在 Dify 工作流里尝试把“代码执行”节点当成最终的图表工厂让这个节点直接输出一串包好 CDN 链接的 ECharts 页面再把这段 HTML 交给前端容器渲染。实测下来效果比让大模型拼 Markdown 强了太多。这篇东西不打算写成官方文档复读只记录我在 Dify 社区版上折腾代码执行节点、用 ECharts 做数据可视化的完整思路。你会看到我从最基础的数据结构定义开始讲一直讲到一个能放进生产环境的工作流中间穿插我踩过的坑和排查方法。1. 为什么一定要用“代码执行”节点而不是让大模型直接写图表1.1 工作流里的数据可视化链条先想清楚一个问题Dify 工作流处理的是数据但最终用户看到的是“界面”。大模型在 Dify 里默认输出的是文本文本再通过 Markdown 渲染成聊天消息。如果你想要柱状图、折线图、饼图Markdown 是做不到的最多用 Mermaid 画个简单的流程图交互还为零。因此数据可视化链条应该是知识库检索、API 返回值、数据库查询结果 - 代码执行节点清洗与转换 - 生成包含 ECharts 的完整 HTML 字符串 - 前端容器加载并渲染在这个链条里面代码执行节点扮演的是“最后一公里”的角色。它把上游那些乱七八糟的 JSON 数据变成一段浏览器能直接识别的 HTML 页面。为什么要放在代码执行节点而不是用大模型去写第一大模型生成 HTML 不稳定经常给你输出残缺的script标签还容易因为转义问题把浏览器搞挂第二大模型输出的内容会消耗 Token你拿去生成图表逻辑纯属浪费第三代码执行节点是确定性的输入什么数据结构输出什么 HTML完全可控方便你测试和排障。我试过在 Dify 的工作流里让大模型直接产出 ECharts 的 option 配置再输出给前端。听起来很美但实际调试时大模型经常把data字段里的值写错或者漏掉xAxis类型声明得反复让大模型自己纠错。后来我彻底放弃这条路让代码执行节点全权接管。1.2 代码执行节点的运行环境和能力边界Dify 的代码执行节点目前主要支持 Python 和 Node.js 两类运行时。我用的是 Python所以在 Dify 社区版 1.x 的环境里这个节点提供的基础库包括json、math、random、re、time等标准库以及部分常用三方库但它毕竟是一个沙箱环境不是让你跑重型训练任务的。对于生成 ECharts 图表这个需求标准库已经完全够用。因为我们要做的不是数据计算而是把上游传过来的 JSON 数据重新组织成 JavaScript 能识别的数据结构然后拼装成一个 HTML 字符串。这里有一个容易忽略的点Dify 代码执行节点要求函数返回类型是dict而且dict里的键必须对应你在节点里声明过的输出变量。如果你不声明输出变量就算返回了内容也不会被读取。沙箱还有超时时间的限制一般是几十秒级别。你的代码如果里面有循环或者数学运算建议控制在百毫秒量级因为 HTML 生成只是字符串拼接理论上几十毫秒就能完成。不要在这个节点里尝试去调用外部 API 抓数据那是 HTTP 请求节点的活。2. 先把数据流设计好代码执行节点才能少踩坑2.1 上游节点结果如何接入代码执行节点我通常会把数据可视化的上游分成三类。第一类是知识库检索结果也就是 RAG 召回的内容这类数据大多是文本想要展示成图表你必须先把文本里的数字提取出来或者让大模型做一次抽取结构化。第二类是外部的 API 返回值比如一个天气接口返回了一堆 JSON这里面可能就包含你想要的温度序列。第三类是数据库查询结果Dify 本身没有直接的数据库节点但你可以用工具节点去查询。无论哪种来源进入代码执行节点之前都要统一成字符串类型的 JSON。比如你在代码执行节点声明一个输入变量叫chart_data类型选 String那么上游传过来的内容就会被当成字符串传入main函数。我建议上游节点用“变量聚合器”或“文本处理”节点把数据先序列化确保传入的变量是标准 JSON 格式。这里有一个实战经验如果你直接让知识库检索的结果作为代码执行节点的输入大概率会遇到两个问题。第一知识库返回的是一个列表里面每个元素是{content: ..., score: ...}你没法直接用于图表第二内容的顺序不稳定今天第一条是 A明天可能变成 B。所以我会先给知识库检索节点加一个“变量聚合器”节点把需要展示的字段重新装配成[{name: A, value: 12}, {name: B, value: 33}]这样的结构再传给代码执行节点。2.2 Schema 定义是代码编写的第一步很多新手在代码执行节点里边写变量名称边定义 Schema改来改去代码都读了十遍最后发现运行报错说“缺少输入变量”。我建议你先在工作流里把输入和输出变量全部定义好再写代码。我这里以最常见的柱状图为例输入变量chart_data类型 String形如[{name: 华东, value: 120}, {name: 华南, value: 200}]输出变量html_content类型 String整个 HTML 页面Dify 代码执行节点的代码模板要求有个main函数并且返回的 dict 里要包含你声明个输出变量缺失任何一个都会导致节点执行失败。你在写代码前先确认这个节点在 UI 的右上角是否已经有对应的输出参数。千万别图省事把输出变量写在函数返回里但节点 UI 没有声明否则等于白写。2.3 用变量聚合器处理多维数据如果你要画的是一个折线图横轴是日期纵轴有 UV 和 PV 两条线那么你的数据输入可能长这样{ dates: [2025-01-01, 2025-01-02, 2025-01-03], uv: [10, 20, 15], pv: [30, 40, 35] }这种结构更适合直接作为代码执行节点的输入因为它对应着 ECharts 里 xAxis 和 series 的天然映射。你不需要在代码里做复杂的 key 映射只要把dates序列化成 JSON 数组字符串把uv、pv也序列化成 JSON 数组字符串就能直接注入到前端。变量聚合器的好处在于它可以把上游多个分支的数据合并成一个结构体。比如你有一个分支是统计注册用户数另一个分支是统计活跃用户数在画折线图时你要的其实是两组不同来源的数据。你用变量聚合器把它们包装成一个大 JSON代码执行节点一次就能读完。3. 实战封装把 ECharts 图表生成器写进 Dify 代码执行节点3.1 柱状图渲染代码拆解我在 Dify 里写的第一个可以上生产环境的代码执行节点只用了十几行核心逻辑但包含了所有必要的转义处理。直接贴出完整代码块供你参考def main(chart_data: str) - dict: 输入格式示例: [{name: 华东, value: 120}, {name: 华南, value: 200}] 输出: 返回一个名为 html_content 的字符串包含完整 ECharts 页面 import json try: data json.loads(chart_data) except Exception as e: return { html_content: fp图表数据解析失败: {e}/p, error: str(e) } # 把 Python 列表转成 JS 数组字符串注意这里别直接用 str() categories [item.get(name, ) for item in data] values [item.get(value, 0) for item in data] categories_js json.dumps(categories, ensure_asciiFalse) values_js json.dumps(values) html f!DOCTYPE html html langzh-CN head meta charsetUTF-8 script srchttps://cdn.jsdelivr.net/npm/echarts5.4.3/dist/echarts.min.js/script /head body div idchart stylewidth: 100%; height: 400px;/div script var chartDom document.getElementById(chart); var myChart echarts.init(chartDom); var option {{ title: {{ text: 业务数据概览, left: center }}, tooltip: {{ trigger: axis }}, xAxis: {{ type: category, data: {categories_js} }}, yAxis: {{ type: value }}, series: [{{ type: bar, data: {values_js}, label: {{ show: true, position: top }}, itemStyle: {{ color: #5470c6 }} }}] }}; myChart.setOption(option); window.addEventListener(resize, function () {{ myChart.resize(); }}); /script /body /html return {html_content: html}这段代码里最关键的是json.dumps的使用。很多新手会直接用str(categories)结果拼出来的 JS 数组是[华东, 华南]这在 JavaScript 里其实是合法的但如果你要保证中文不乱码json.dumps(categories, ensure_asciiFalse)会输出[华东, 华南]这样最稳妥。另外在 f-string 里写 JavaScript 对象时花括号必须双写转义所以你看到{{ text: 业务数据概览, left: center }}这个双花括号最终渲染出来是一个单花括号的 JS 对象字面量不会触发 Python 的格式化报错。3.2 折线图和饼图的封装技巧折线图只需要在上面的基础上改一下option的结构但要注意多个 y 轴数值可能就不是一个xAxis能搞定的了。这里贴一个我常用的多序列折线图生成代码def main(series_data: str) - dict: import json try: data json.loads(series_data) except Exception: return {html_content: p解析失败/p} dates_js json.dumps(data.get(dates, []), ensure_asciiFalse) uv_js json.dumps(data.get(uv, [])) pv_js json.dumps(data.get(pv, [])) html f!DOCTYPE html html head meta charsetUTF-8 script srchttps://cdn.jsdelivr.net/npm/echarts5.4.3/dist/echarts.min.js/script /head body div idchart stylewidth: 100%; height: 440px;/div script var chartDom document.getElementById(chart); var myChart echarts.init(chartDom); var option {{ tooltip: {{ trigger: axis }}, legend: {{ data: [UV, PV] }}, xAxis: {{ type: category, data: {dates_js} }}, yAxis: {{ type: value }}, series: [ {{ name: UV, type: line, data: {uv_js}, smooth: true }}, {{ name: PV, type: line, data: {pv_js}, smooth: true }} ] }}; myChart.setOption(option); window.addEventListener(resize, function () {{ myChart.resize(); }}); /script /body /html return {html_content: html}饼图和柱状图的区别在于series里的type换成pie同时xAxis和yAxis要删除因为饼图不需要二维坐标系。还有一个很常见的细节饼图上如果要在圆环中间显示总数ECharts 5 提供了series.label和graphic或title的组合方案。我先直接给一个最朴素的饼图 option你在真实项目里可以继续加中心文字var option { tooltip: { trigger: item }, series: [{ type: pie, radius: [40%, 70%], data: [ { value: 120, name: 华东 }, { value: 200, name: 华南 } ], label: { show: true, formatter: {b}: {c} } }] };这部分的代码不用我每个字段都讲解你已经看出规律代码执行节点里的核心工作是把 Python 数据结构转成 JSON 字符串剩下的只是 ECharts 本身的配置知识。3.3 别忘了容器尺寸和图表自适应这段是比较容易被忽略的细节。Dify 最终把html_content输出到你的网页或仪表盘时容器可能没有固定高度。如果 HTML 里div的height直接设置成400px在某些移动端视口中可能被截断或显示不全。建议方案是在div的样式中写width: 100%; height: 100%;然后给外层容器设置最小高度min-height: 360px;。或者干脆使用height: 400px兼容性最好就是不够“响应式”。同时一定要写监听窗口变化的事件因为在网页里用户调整浏览器大小、切换标签页图表不会自动重绘需要手动调用resize()。这段代码在 f-string 里的写法已经写进示例了注意双花括号转义。4. 组合进工作流输出、调试与性能考量4.1 从代码执行节点到回复节点的链路代码执行节点生成完html_content之后你需要决定怎么把它展示给用户。两个常见的做法。第一用“直接回复”节点把html_content作为文本输出这种情况适合你做 API 输出自己写前端前端拿到这个 HTML 后直接渲染。第二在应用编排里的最后一步使用一个“变量聚合器”或“文本处理”节点把html_content塞进一条消息里发送给用户但这只适合用户用浏览器访问时前端做了特殊处理。如果你用的是 Dify 自带的 WebApp 聊天界面请注意一点默认聊天界面不会渲染包含完整html标签的 HTML 代码。这是出于安全的考虑我一开始不知道使劲把div和script往消息里塞结果页面只显示了源码看了半天才发现是前端过滤了 HTML。所以我推荐的链路是代码执行节点把html_content输出给一个文本类型的回复节点同时你的前端应用通过 Dify 的 API 拿到这段 HTML 后用一个iframe或者一个容器元素进行渲染。如果你把图表作为仪表盘的一个组件可以在仪表盘里使用内嵌 HTML 组件直接绑定变量也能规避聊天界面的渲染限制。4.2 调试技巧本地 Python 环境快速验证Dify 的代码执行节点自带“运行”按钮但那个调试面板只显示最终返回的 dict不能直接看网页效果。我在本地调试时有个固定套路先把 Dify 里要用的数据复制出来存成一个 JSON 文件然后在本地 Python 环境跑一遍这个main函数把返回的html_content写成一个.html文件用浏览器打开。用浏览器预览有一个好处你可以打开开发者工具看到 ECharts 是否真的初始化成功有没有报 error。如果是 CDN 链接挂了页面上会出现echarts is not defined的错误提示这就跟你的代码无关得换成稳定的 CDN 或自建静态资源服务器。调试时一定要模拟 Dify 的环境因为你本地的 Python 版本可能是 3.11但 Dify 沙箱可能是 3.10。代码里不要用 3.11 才有的高级语法特性比如Self类型tomllib这些可能导致运行时报语法错误。我一般故意用最基础的json和f-string语法越小众越安全。4.3 上下文长度与执行超时的经验之谈代码执行节点输出一个几十 KB 或上百 KB 的 HTML 字符串对 Dify 工作流本身不会造成致命影响因为这段字符串不会直接塞给大模型。但你要知道如果这个html_content被后续的大模型节点引用那就会在提示词里占大量 Token。因此我会把代码执行节点放在大模型处理完之后换言之它应该是当前分支的末尾节点不要让大模型再对 HTML 做二次加工。如果你的数据类别特别多比如柱状图有 200 个分类标签生成的 HTML 里全部写出来可能超过 100 KB。这种超长字符串在进行日志记录或前端传输时都可能拖慢体验。我的建议是在代码里加个逻辑如果数据长度超过某个阈值先按数值排序只保留 Top 20 的类别其余全部合并为“其他”。这样做图表更清晰也不会造成上下文过长。5. 常见问题与排查实录5.1 JSON 数据传进来被解析成字符串而不是对象Dify 的代码执行节点里输入变量类型选 String 的话传入的就是一整个字符串你在代码里必须先用json.loads转成 Python 对象。但有的时候前端传过来的数据是经过百分号编码或者包含转义符号的导致json.loads失败报Expecting property name enclosed in double quotes。我的处理经验是在上游节点加一个“文本处理”节点先对输入变量做一次replace操作把单引号替换成双引号手动去掉多余的换行符和制表符。Dify 的文本处理节点支持这些常用字符串操作。如果你发现 JSON 解析老报错别急着改代码先去检查上游节点的输出。5.2 代码执行节点一直显示“执行失败”但不能定位到具体哪行Dify 的代码执行节点默认只显示最后一段报错比如KeyError: value但不会直接告诉你哪行代码出了问题。我用了一个很土但有效的办法在关键取值步骤加一个try...except把异常信息拼到返回的 HTML 里。这样就算执行失败你也能在返回内容里看到具体的错误信息不用靠猜测。还有一点Dify 的代码执行节点内置的import json不一定要写在函数外面写在函数内部也是可以的。如果你导入了沙箱不存在的第三方库节点会直接报“No module named”。因此我提供的示例只用了标准库想要用三方库前先查阅 Dify 官方文档确认有哪些库可用。5.3 在 Windows 本地部署里遇到环境相关的报错有些朋友在 Windows 上部署 Dify 社区版时可能会遇到类似“找不到 msvcp140.dll无法继续执行代码”的提示或者 ssl 证书验证失败这其实和代码执行节点生成图表没有直接关系但它会阻碍你用浏览器调试。遇到这些建议先安装 Visual C 运行库VC_redist.x64.exe同时确保 Docker Desktop 使用 WSL2 后端然后重启。Dify 本身也涉及很多前后端服务比如 Nginx、API、Worker如果调试时发现 CDN 加载不出来可能是网络环境问题。你在代码执行节点里拼接的 HTML 如果直接引用 Google 或某些外部 CDN加载速度可能很不稳定。我在国内环境里一般会改成https://cdn.jsdelivr.net/npm/echarts5.4.3/dist/echarts.min.js或者干脆把 ECharts 的 js 文件上传到自己的对象存储以静态资源链接的形式写入 HTML这样最可控。5.4 仪表盘 HTML 组件绑定变量后的刷新问题Dify 的仪表盘组件允许绑定变量但如果你绑定的变量是html_content数据更新后图表并不一定自动重绘。我的经验是在代码执行节点生成 HTML 时顺手生成一个last_updated时间戳变量放到页面的某个隐藏字段或标题里。仪表盘因为依赖数据变化而重新渲染有了这个时间戳至少能保证你看到页面是哪一次运行的结果排查定位也方便。另外在一个工作流里生成多个图表你可以通过多个代码执行节点分别输出html_content_1、html_content_2这样的变量用同一个页面容器分区块展示。不要让一个节点生成一整张大页面否则可维护性特别差容易在拼接时把单引号转义搞崩。6. 最终小节我实际用下来的几个体会如果你已经把前面的代码复制到 Dify 里跑通了那恭喜你现在拥有了一个稳定的“图表工厂”。我个人在实际操作中的体会是代码执行节点最怕的不是处理复杂转换而是收到结构不稳定的上游数据。所以我后来给这个节点加了一层“数据体检”逻辑约束输入必须是标准 JSON并且每个对象都包含name和value字段缺了就直接报错而不是输出一个空图表。还有一个习惯每当我需要画新图表类型的时候我会先把需求简化到最小的 JSON 数据结构再写代码。这种从数据倒推代码的方法能让我在 Dify 的工作流里少做很多无用的变量映射。最后分享一个小技巧代码执行节点输出 HTML 字符串后记得在 HTML 的meta标签里声明charsetUTF-8。我之前漏掉过结果图表上的中文一层乱码排查了半小时才发现是浏览器按默认编码渲染了页面。加上这行以后所有中文都正常了。代码执行节点虽然小配合 ECharts 能做的事情却不少从业务监控到数据简报它都可以成为你工作流里最高效的可视化出口。
返回列表