在做企业内部数据答疑机器人的时候,我有一个很深的体会:很多业务方并不满足于“回答是什么”,而是希望AI直接给出可视化结果。比如“各地区销售额占比怎么样”这类问题,如果能直接回一张饼状图,沟通效率会高很多。这个需求看着简单,实际落地时却在 Dify 里卡了很久——网上资料大多在讲 RAG 和 Agent,真正讲“如何稳定输出一张饼状图”的内容很少。本文就从零梳理在 Dify 中一键生成饼状图的几种落地路线,覆盖工作流、代码节点、数据分析 Agent 和前端 ECharts 集成,附上完整代码和踩坑记录,适合正在用 Dify 搭建报表类应用的开发者参考。
1. 背景与核心概念
1.1 Dify 是什么
Dify 是一个开源的 LLM 应用开发平台,提供了从模型管理、Prompt 编排、知识库到工作流、Agent、API 发布的一整套能力。你可以把它理解成一个“大模型应用的后台”,不用从零编写前端页面,也不用自己维护复杂的会话状态,就能快速搭建聊天助手、智能客服、数据分析助手这类应用。
在数据分析场景中,Dify 的价值主要体现在两点:一是把“用户输入—模型理解—工具调用—结果输出”的完整链路可视化地串起来;二是通过工作流和代码节点,把传统脚本里的数据处理逻辑嵌入到 AI 应用流程中。
1.2 一键生成饼状图的业务场景
饼状图在业务分析里非常高频,常见场景包括:
- 用户上传一份销售明细,希望看到各产品线销售额占比。
- 运营人员输入几个分类和数值,想快速得到预算分配比例。
- 企业报表机器人需要定时生成部门人数、渠道来源、费用结构等占比图。
- 前端集成需求:AI 通过对话得出结构化数据,再由自研系统渲染成交互式图表。
这些场景的共同点是:用户不想手动复制数据到 Excel,也不愿意在报表工具里重新配置数据源。通过 Dify,我们可以把“数据解析、占比计算、绘图展示”封装成一条自动化链路。
1.3 三条常用技术路线对比
| 实现方案 | 输出形式 | 交互性 | 适用场景 |
|---|---|---|---|
| 工作流 + 代码节点 | 静态图片 / base64 | 低 | Dify 聊天窗口内直接展示 |
| 数据分析 Agent | 图片 / 文字分析 | 中 | 上传 CSV、Excel 文件后自然语言生成图表 |
| JSON 数据 + 前端 ECharts | 结构化数据 | 高 | 自研前端页面,需要图例交互、联动筛选 |
三条路线并不冲突。在同一个项目里,可以先在 Dify 中用代码节点验证效果,后续再切换到 Agent 或前端渲染方案。后面我会逐一展开。
2. 环境准备与平台说明
2.1 准备条件
在开始之前,你需要准备好以下环境:
- Dify 实例:社区版 Docker 部署、企业版或云端 SaaS 均可。
- 模型供应商 API Key:用于创建 Agent 或 LLM 节点,推荐选择支持工具调用的模型。
- 工作流创建权限:至少能创建一个空白工作流应用。
- 测试数据:建议准备 JSON 格式的占比数据,也可以准备 CSV 文件。
需要注意的是,不同 Dify 版本的节点类型、工具名称、API 端点会有差异。本文不会绑定某个具体版本,以“自托管 + 较新版本”为例,重点说明配置思路和通用写法。如果你使用的是云端免费版,部分自定义依赖可能受限,需要根据你的实际环境调整。
2.2 测试数据准备
为了方便演示,我们先准备一份 JSON 格式的数据,结构非常简单,包含分类名称和数值:
[ {"name": "华东区域", "value": 320}, {"name": "华南区域", "value": 260}, {"name": "华北区域", "value": 180}, {"name": "西南区域", "value": 90} ]如果你更习惯用表格文件,也可以用 CSV:
区域,销售额 华东,320 华南,260 华北,180 西南,90本文后面的示例都基于这份数据。实际项目中,数据可能来自数据库、Excel 导入或用户对话,但核心处理逻辑是一样的:把不同来源的数据统一成[{name, value}]这样的结构,再交给绘图环节。
2.3 关键技术约束说明
在 Dify 中生成饼状图,最大的技术约束是代码节点的沙箱环境。
Dify 的代码节点运行在独立的沙箱容器中,和本地 Python 环境并不完全一致。沙箱默认提供基础 Python 库,但第三方库并不一定齐全。比如你想直接import matplotlib,需要确认当前部署镜像中是否安装了 matplotlib。如果用的是自托管 Docker 部署,可以通过修改镜像或安装依赖解决;如果用的是云端托管版本,则可能需要改用数据分析 Agent 或自定义工具。
理解这一点,后面遇到ModuleNotFoundError时就不会慌。
3. 核心原理拆解
3.1 生成饼状图的本质
饼状图本质上是一个“数据映射”问题:你需要有分类字段和数值字段,然后由绘图库把数值转换成角度。所以,不管 Dify 的方案怎么变,核心步骤都是固定的:
- 接收原始数据。
- 清洗并解析数据。
- 计算占比(一般由绘图库自动完成)。
- 渲染成图片或结构化数据。
- 返回给用户或前端系统。
Dify 的工作流非常适合承载这套流程。开始节点负责接收输入,代码节点负责数据处理和绘图,输出节点负责展示结果。
3.2 图片输出的三种形式
在 Dify 中,把图片展示给用户,常见有三种形式:
- base64 Data URL:把图片编码成
data:image/png;base64,xxx,可以直接塞进 Markdown 的![]()语法中,适合小尺寸图片。 - 文件输出:如果你的 Dify 版本支持文件类节点,可以把图片作为文件返回,用户可以直接下载。
- 外部 URL:把图片上传到对象存储或图床,返回一个可访问的 URL,适合大图和跨端展示。
base64 方式实现最简单,但也最容易踩坑。图片过大时,聊天消息会变得很长,部分前端渲染可能不稳定。我们会在后面常见问题部分专门说明。
3.3 Dify 代码节点的执行逻辑
Dify 工作流中的代码节点,入口函数通常命名为main,参数来自上游节点,返回值是一个字典,字典中的字段会成为该节点的输出变量。了解这个逻辑后,写代码节点时就不会手足无措。
代码节点适合做三件事:数据格式转换、数据清洗、调用绘图库生成图片。但要注意,代码节点不是万能的,它不适合做重量级模型调用,也不适合执行需要访问内网数据的操作。如果业务复杂,建议把能力封装成自定义工具,而不是把大量逻辑堆在代码节点里。
4. 实战方案一:工作流 + 代码节点绘制饼状图
4.1 创建应用与开始节点
登录 Dify 控制台,选择“工作流”类型,创建一个新的工作流应用。
进入工作流画布后,先配置“开始”节点。这个节点负责接收外部传入的参数。我们定义两个输入字段:
| 字段名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| data | string | 是 | JSON 数组,格式为[{"name":"分类","value":数值}] |
| title | string | 否 | 图表标题,默认“数据分布” |
从语义上讲,data是绘图的核心数据源,title只是辅助展示,后续代码节点会用到。
4.2 编写绘图代码节点
在画布中添加一个“代码”节点,将开始节点作为上游。代码节点的参数映射中,把开始节点的data和title传入。
绘图的代码如下,可以直接复制到 Dify 代码节点的编辑器中:
import json import base64 from io import BytesIO def main(data: str, title: str) -> dict: # 1. 解析 JSON 数据 try: items = json.loads(data) except Exception as e: return {"error": f"JSON解析失败:{e}"} if not isinstance(items, list) or len(items) == 0: return {"error": "数据不能为空,且必须是列表"} # 2. 提取分类和数值 names = [] values = [] for item in items: names.append(str(item.get("name", ""))) values.append(float(item.get("value", 0))) # 3. 使用 matplotlib 绘制饼状图 try: import matplotlib matplotlib.use("Agg") import matplotlib.pyplot as plt # 字体设置,避免中文乱码 plt.rcParams["font.sans-serif"] = ["SimHei", "Arial Unicode MS", "DejaVu Sans"] plt.rcParams["axes.unicode_minus"] = False fig, ax = plt.subplots(figsize=(6, 5)) ax.pie(values, labels=names, autopct="%1.1f%%", startangle=90) ax.axis("equal") ax.set_title(title or "数据分布") # 4. 转成 base64 字符串 buf = BytesIO() fig.savefig(buf, format="png", dpi=120, bbox_inches="tight") buf.seek(0) img_base64 = base64.b64encode(buf.read()).decode("utf-8") plt.close(fig) total = sum(values) return { "image_base64": img_base64, "data_url": f"data:image/png;base64,{img_base64}", "total": round(total, 2) } except ModuleNotFoundError as e: return {"error": f"当前沙箱缺少绘图依赖:{e};请改用数据分析 Agent 或自定义工具"}代码里有几个关键点:
matplotlib.use("Agg")是必须的。Dify 代码节点运行在无图形界面的服务器上,只有使用 Agg 这种非交互式后端,savefig才能正常导出 PNG 图片。- 中文字体设置了多个候选,因为不同系统内置字体不一样。如果仍然乱码,说明环境中没有这些中文字体,可以改用英文标签,或通过自定义工具补齐字体文件。
try-except包裹绘图逻辑,是为了在沙箱缺少依赖时返回友好的错误信息,而不是让整个工作流直接失败。- 返回的
data_url是一个可直接用于 Markdown 图片的 Data URL,省去前端拼接的步骤。
4.3 配置输出节点
添加一个“输出”节点,输出类型选择 Markdown。在输出内容中引用代码节点的输出变量:
## 饼状图  > 数据总量:{{#代码节点ID.total#}}需要注意,代码节点ID要替换成你画布中实际节点的 ID,变量引用方式可以参考 Dify 工作流编辑器提供的变量提示。Dify 在画布中会直接显示可用的输出变量,不需要手动记忆节点 ID。
如果你希望图片可下载而不是嵌入聊天,可以尝试在输出节点中返回文件类型,但这依赖你的 Dify 版本是否提供文件输出节点。基础用法还是推荐 Markdown base64 方式。
4.4 运行与验证
点击右上角的“运行”按钮,输入下面的测试数据:
[ {"name": "华东区域", "value": 320}, {"name": "华南区域", "value": 260}, {"name": "华北区域", "value": 180}, {"name": "西南区域", "value": 90} ]运行后,预期输出是一张饼状图,四个区域会按数值比例显示,同时给出数据总量。如果一切正常,你已经实现了“一键生成饼状图”的最小闭环。
4.5 加入 LLM 节点支持自然语言输入
代码节点目前要求输入必须是标准 JSON,对用户不够友好。如果希望用户直接输入“华东 320,华南 260,华北 180,西南 90”,可以在代码节点前面加一个 LLM 节点,让模型把自然语言转成 JSON。
LLM 节点的 Prompt 可以这样设计:
请从用户输入中提取饼状图需要的分类和数值。 输出格式必须为 JSON 数组,例如: [{"name":"华东","value":320}] 只输出 JSON,不要输出其他解释。 用户输入:{{#开始节点.query#}}但大模型输出的 JSON 不一定完全规范,所以仍然需要在代码节点中做json.loads容错。这也是“模型负责理解、代码负责稳定”的典型分工。
5. 实战方案二:数据分析 Agent 一键生成饼状图
5.1 适用场景
方案一的缺点是用户必须按固定格式录入数据,而且绘图依赖沙箱中的 matplotlib。如果业务方希望直接上传 Excel 或 CSV,再由 AI 自动完成分析和绘图,方案二会更合适。
这是典型的数据分析 Agent 场景:用户上传文件,Agent 调用代码解释器或数据分析工具读取文件、理解列名、绘制图表。整个过程不需要用户关心 JSON 格式,也不需要预先设计工作流分支。
5.2 创建 Agent 与配置工具
在 Dify 控制台创建一个“Agent”应用,选择支持工具调用的模型,然后在工具列表中启用代码执行类工具。不同版本的工具名称可能不同,有的叫“数据分析”,有的叫“代码解释器”,请以你的环境为准。
如果工具列表里没有,可以查看是否支持安装自定义工具或插件,把绘图能力封装成一个工具后,在 Agent 中启用。这个方案适合自托管部署的场景,云端版本受限于平台能力,需要关注当前版本的特性说明。
5.3 设计提示词
数据分析 Agent 的提示词很重要,直接影响模型理解和执行效果。推荐下面这个模板:
你是一名数据分析师。请根据用户上传的数据文件,完成以下任务: 1. 理解数据中的分类字段和数值字段。 2. 如果用户要求生成饼状图,请选择合适的字段。 3. 使用代码解释器绘制饼状图并返回图片。 4. 如果分类过多,只展示占比最大的前 N 项,其余合并为“其他”。 5. 最终用简洁的中文说明数据结论,并指出占比最高的分类。这段提示词做了三件事:明确角色、限定输出、处理极值情况。第 4 条尤其重要,因为当分类太多时,直接画饼状图会变成一个密密麻麻的圆环,可读性很差。让 Agent 自动合并“其他”,是最实用的工程技巧。
5.4 执行流程与注意事项
用户上传文件后,Agent 会经历以下流程:
- 分析文件结构,识别列名。
- 根据用户问题选择分类列和数值列。
- 调用代码解释器,用 Python 绘制饼状图。
- 返回图片和文字分析。
在使用时,有几个问题需要提前考虑:
- 中文列名:模型可能无法准确识别类似“区域-销售额”这样的字段,建议在 Prompt 中要求模型先输出字段识别结果,再继续绘图。
- 数据脱敏:如果文件包含客户姓名、手机号等敏感信息,建议先过滤后再上传。
- 数值字段类型:如果列中混有空值或非数字,模型需要先做清洗。你也可以在 Prompt 中强制要求“先处理缺失值,再绘图”。
在私有化部署的环境中,上传文件不会离开内网,安全性相对可控。如果使用云端版本,涉及敏感业务数据时,务必确认平台的数据保护方案。
6. 实战方案三:输出 JSON 数据 + 前端 ECharts 渲染
6.1 适用场景与整体架构
方案一和方案二生成的图片是静态的,用户无法悬停查看精确数值,也无法点击某个扇区做下钻。如果 Dify 只是后端能力的一部分,最终页面在自己的业务系统里,更推荐方案三:Dify 负责生成结构化数据,前端使用 ECharts 渲染交互式饼图。
整体架构如下:
用户输入 → Dify 工作流 → 代码节点清洗数据 → 输出 JSON → 业务后端调用 Dify API → 前端 ECharts 渲染这种方案把“AI 能力”和“前端展示”解耦,Dify 不直接输出图片,而是输出一张“数据加工单”。
6.2 Dify 工作流返回结构化数据
在 Dify 工作流中,代码节点只需要负责清洗和格式化数据,不需要调用 matplotlib。示例代码如下:
import json def main(data: str) -> dict: items = json.loads(data) # 清洗非法数据 cleaned = [] for i in items: name = str(i.get("name", "")) value = i.get("value") if name and value is not None: try: cleaned.append({ "name": name, "value": float(value) }) except ValueError: continue return { "chart_data": json.dumps(cleaned, ensure_ascii=False) }输出节点直接返回chart_data字段,内容是一个标准 JSON 数组。前端拿到就能直接使用,不需要再做二次解析。如果你希望在 Dify 聊天窗口里做预览,也可以加一个 Markdown 输出节点,把 JSON 以代码块形式展示。
6.3 调用 Dify API 获取结果
如果前端需要主动触发生成图表,通常通过后端调用 Dify 的开放 API。这里给一个 Python 调用示例,需要注意的是,不同应用类型的 API 端点不同,工作流应用一般使用/v1/workflows/run,聊天助手应用使用/v1/chat-messages,以你环境中的 API 文档为准:
import requests URL = "http://your-dify.example.com/v1/workflows/run" API_KEY = "app-xxxx" payload = { "inputs": { "data": '[{"name":"华东","value":320},{"name":"华南","value":260}]' }, "response_mode": "blocking", "user": "demo-user" } headers = { "Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json" } resp = requests.post(URL, json=payload, headers=headers, timeout=60) print(resp.status_code) print(resp.json())接口返回结果中会携带工作流输出字段,你需要根据实际响应结构解析出chart_data。如果响应超时,可以把response_mode改为streaming,用流式方式逐步读取。
6.4 前端 ECharts 展示
前端拿到chart_data后,用 ECharts 渲染非常方便。下面是一个可直接运行的 HTML 示例:
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <title>饼图示例</title> <script src="https://cdn.jsdelivr.net/npm/echarts@5/dist/echarts.min.js"></script> </head> <body> <div id="chart" style="width: 600px; height: 400px;"></div> <script> // 实际项目中,这里的数据来自 Dify API 返回的 chart_data const data = [ { name: '华东区域', value: 320 }, { name: '华南区域', value: 260 }, { name: '华北区域', value: 180 }, { name: '西南区域', value: 90 } ]; const chart = echarts.init(document.getElementById('chart')); chart.setOption({ tooltip: { trigger: 'item' }, legend: { bottom: 0 }, series: [{ type: 'pie', radius: '60%', data: data, label: { formatter: '{b}: {d}%' }, emphasis: { itemStyle: { shadowBlur: 10, shadowOffsetX: 0, shadowColor: 'rgba(0, 0, 0, 0.5)' } } }] }); </script> </body> </html>ECharts 的好处是交互能力强,用户可以 hover 查看百分比、点击图例筛选分类,更适合正式的业务系统。相比静态图片,这种方案也更适合做下钻分析,比如点击“华东区域”后继续展示该区域下的城市分布。
7. 常见问题与排查思路
7.1 高频问题速查表
| 问题现象 | 常见原因 | 解决思路 |
|---|---|---|
代码节点报错ModuleNotFoundError: No module named 'matplotlib' | 沙箱环境未安装绘图库 | 使用数据分析 Agent 或自定义工具;自托管时在沙箱镜像中安装依赖 |
| 生成的图片不显示,只看到一长串 base64 | 前端不支持过长 Data URL | 缩小图片尺寸;改用文件节点或对象存储 URL |
| 图片中文标签乱码 | matplotlib 缺少中文字体 | 配置中文字体;或使用英文标签 |
| JSON 解析失败 | 大模型输出不是标准 JSON | 代码节点中做容错;增加 LLM 节点前的数据清洗 |
| 饼图扇区太多,无法阅读 | 分类数量过多 | 只展示前 N 项,其余合并为“其他” |
| 工作流输出变量引用不生效 | 节点 ID 或字段名写错 | 在画布中通过变量面板确认引用名 |
| Agent 无法识别上传文件的列名 | 列名是中文且有歧义 | 在 Prompt 中要求先输出字段识别结果再绘图 |
7.2 依赖缺失问题的排查流程
如果你在代码节点中遇到依赖缺失,可以按下面的顺序排查:
- 先查看报错日志,确认是
ModuleNotFoundError还是ImportError。 - 如果你使用的是 Dify 社区版 Docker 部署,找到沙箱镜像,确认是否进入容器执行过
pip list。如果缺依赖,可以在自定义镜像中补充安装。 - 如果你使用的是云端版本,代码节点无法修改环境,建议改用数据分析 Agent 或自定义工具,而不是继续折腾沙箱。
- 如果业务必须使用 matplotlib,但环境不支持,可以考虑将绘图逻辑下沉到独立服务,通过 HTTP 请求节点调用。
7.3 base64 图片过长问题
base64 图片本质上是把二进制文件转成纯文本,体积会比原图增加约 33%。当图片达到几 MB 时,聊天消息会非常卡顿。解决方法主要有:
- 降低
dpi到 96 或 72,减小figsize,优先保证清晰度够用。 - 使用 PNG 压缩参数,或在
savefig中设置optimize=True。 - 改用文件输出或对象存储 URL,适合需要长期保存的报表。
- 如果只是临时预览,可以接受较低分辨率。
8. 最佳实践与工程建议
8.1 输入数据严格校验
无论是工作流代码节点还是 Agent,输入数据都可能来自用户自由输入。校验是第一步,也是最重要的一步。我们在绘图代码中要检查数据类型、空列表、None 值、负数等情况。如果数据不合法,返回一个明确的错误信息,而不是让工作流崩溃。
if not items: return {"error": "数据为空,请检查输入"} if any(v < 0 for v in values): return {"error": "饼状图不支持负数,请检查数值字段"}这种“提前失败”的设计,可以显著降低后续排查成本。
8.2 图片输出优化
- 控制图片体积:
figsize和dpi不要盲目调大,6x5 英寸、120 dpi 已经足够聊天窗口展示。 - 设置中文字体:把字体配置统一封装成公共代码,避免每个工作流都重复编写。
- 数据过多时截断:只画 Top 10,其余合并为“其他”,保证可读性。
- 尽量在 Python 中完成百分比格式化,避免前端或模型二次处理。
8.3 绘图逻辑复用与自定义工具
在一个实际项目中,多个工作流可能都需要生成饼状图。如果每个工作流都复制一份绘图代码,后续维护会非常痛苦。推荐的做法是:把绘图逻辑封装成 Dify 的自定义工具。工具内部可以包含更复杂的 Python 代码、字体资源、依赖管理,工作流只需要传入数据和参数即可。
如果你不想马上做自定义工具,至少可以把公共代码集中在一个代码节点中,通过上游节点把参数传入,减少重复。
8.4 安全与权限建议
- 上传到 Dify 的数据可能包含敏感信息,在进入工作流之前,做好脱敏。
- 如果使用云端 SaaS,避免把涉及客户隐私、财务明细的文件直接上传。
- 调用 Dify API 时使用最小权限的 API Key,不要使用管理员密钥。
- 如果要在聊天窗口中展示 HTML 或图片,注意过滤用户输入,避免恶意内容注入。
- 私有化部署是数据合规性要求较高时的首选方案。
这些建议同样适用于其他 Dify 应用,不只是饼状图场景。
9. 总结与后续学习方向
本文围绕 Dify 一键生成饼状图,介绍了三条完整落地路线:
- 工作流 + 代码节点:适合在 Dify 聊天窗口内快速展示静态图片,通过 base64 输出。
- 数据分析 Agent:适合用户直接上传 CSV、Excel 文件,由模型自动完成分析和绘图。
- JSON 数据 + ECharts:适合自研前端系统,通过 Dify API 获取结构化数据,再由 ECharts 渲染交互式图表。
实际项目里,没有绝对最优的方案。如果让我重新做一次,我会先确认部署环境的沙箱依赖,再决定是用代码节点还是 Agent,最后根据前端需求选择静态图片还是交互式图表。不同 Dify 版本对代码节点、工具、API 端点的支持存在差异,强烈建议你在自己的环境中先用示例数据跑一遍,确认沙箱依赖和变量引用方式后再应用到业务数据。
如果这篇内容对你有帮助,可以收藏备用。后续如果对 Dify 自定义工具、Agent 配置或前端集成有更多疑问,也欢迎继续交流。