做数据分析的同学几乎每天都要回答同一个问题这张饼图帮我看看哪块业务占比最大。传统流程是先导数据、清洗格式、写一段 matplotlib 或者拖一个图表组件再生成一张没有名字的figure_1.png发给同事。整个过程都在做与业务无关的体力活。如果这个“看图”需求来自业务负责人、市场运营、售前顾问这类非技术角色那瓶颈就变成了他们手里有数字却在“把数字转成图”这件事上被卡住了。Dify 这类大模型应用开发平台出现之后把这条路径明显缩短了。用户只需要输入一句自然语言比如“把 2024 年四个季度的销售额画成饼图”底层的 LLM 负责把这句话转成结构化数据工作流里的代码节点负责把结构化数据转成一张可渲染的 SVG 图表。整个过程不需要自己写接口、不需要维护前端页面也不需要教对方使用命令行。这篇文章不打算只给一个“复制粘贴就能跑”的示例而是把这条链路拆开讲清楚为什么用 Dify 搭这类小工具是划算的Dify 的核心概念如何理解饼状图这类图表生成的几种实现路径该怎么选以及从本地部署、工作流搭建到效果验证和排错的完整实操。读完你可以照着搭出一个能用的“一句话生成饼状图”应用也能把这个思路迁移到柱状图、折线图、仪表盘报表等更多场景。1. 这篇文章真正要解决的问题先说一个容易误判的地方很多人觉得“Dify 一键生成饼状图”就是一个 AI 画图工具让大模型直接画图就行。但在实际工程里直接让 LLM 输出一张图片并不可靠。大模型擅长的是语义理解、信息抽取和文本生成它不擅长精确计算坐标、颜色、半径、角度这些图形学问题。如果你让它直接生成一张饼图它可能给你一段 HTML也可能给你一段 SVG格式不稳定数值位置也常常出错。真正值得做的是把任务拆成两步LLM 负责把自然语言转成结构化数据代码节点负责用确定性的逻辑把结构化数据渲染成图表。Dify 的核心价值就在这里。它不只是一个聊天机器人平台而是一个把 LLM 能力、工作流编排、API 接入、知识库和工具调用组合起来的应用开发平台。你可能听过“Dify 智能体平台”“Dify 工作流”这些说法本质上它解决的是同一个问题让开发者用更少的代码把大模型能力封装成可调试、可复用、可上线的业务应用。这篇文章适合以下几类读者刚开始接触 Dify想找一个小而完整的项目练手团队里有“业务人员提数据需求研发写图表代码”的协作场景想用 AI 应用提高效率已经用 Dify 做过聊天助手但还没深入用过工作流和代码节点想理解“为什么 AI 应用不能全靠大模型输出”这个工程问题的开发者。读完你会得到一个可运行的 Dify 工作流应用输入自然语言输出一个带图例、带百分比标签的 SVG 饼状图。同时你也会知道这套做法在什么情况下会失效生产环境里应该怎么加固。2. Dify 核心概念与工作流原理在进入实操之前先花三分钟理解 Dify 的几个核心概念。这些概念后面都会用到。2.1 Dify 是什么Dify 是一个开源的大语言模型应用开发平台。你可以把它理解成一个“AI 应用的后台”它能管理多个模型供应商的 API Key能配置应用的类型和提示词能可视化编排工作流也能把应用发布成 API 或网页应用。它和直接调用 OpenAI API、DeepSeek API 的区别在于Dify 把很多通用的工程细节沉淀成了平台能力。比如模型切换、提示词版本管理、日志监控、知识库 RAG、多应用管理这些在原生 API 开发里都需要自己写在 Dify 里是开箱即用的。2.2 应用类型聊天助手、Agent 与工作流Dify 里创建应用时通常有几种类型可选应用类型适合场景特点聊天助手多轮对话、客服、答疑依赖对话记忆交互自然但输出可控性弱Agent需要调用工具、搜索、计算等外部能力自主决策适合复杂任务但调试难度高工作流固定流程、确定输出、批量处理节点明确每一步可调试输出可控性强“一键生成饼状图”本质是一个流程固定的任务理解输入、抽取数据、生成图表、返回结果。它不需要 Agent 去自主决定调用哪个工具也不需要多轮对话记忆。所以更合适的选择是工作流应用。2.3 工作流节点与变量传递Dify 工作流的核心是两个东西节点和变量。节点是处理单元常见的有开始节点接收外部输入LLM 节点调用大模型让它做理解、抽取、生成代码节点执行 Python 或 Node.js 代码做确定性处理条件分支根据变量值走不同路径HTTP 请求节点调用外部服务结束节点把结果返回给调用方。变量是节点之间的数据通道。上一个节点的输出可以作为下一个节点的输入。比如 LLM 节点输出的 JSON 字符串可以传给代码节点代码节点处理后再传给结束节点。这种“节点 变量”的设计把一次完整的应用请求拆成了一条可观察、可调试的流水线。对比传统开发它的优势在于维度传统代码开发Dify 工作流调试需要写日志、断点界面直接查看每个节点输入输出修改改代码、重新部署改节点配置保存即可生效模型替换改代码、切 SDK界面上切换模型供应商多人协作代码评审、环境管理可视化 版本管理这个设计背后的原因是LLM 的输出天然带有不确定性把“理解”和“计算”拆到不同节点里就可以让不确定的部分只在 LLM 节点出现后面用代码节点去约束结果。这也是 Dify 工作流适合做图表生成的根本原因。3. 生成饼状图的三种实现路径与选型先不急着配环境我们需要选一条合适的实现路径。生成饼状图在 Dify 里至少有三种做法现实情况和你想的可能不太一样。3.1 路径 ALLM 直接输出 HTML / ECharts / SVG这是最直接的做法在提示词里要求模型输出一段包含饼图的 HTML 代码或者输出 ECharts 的 option 配置。用户拿到这段代码后在浏览器里打开就能看到图表。优点是最快只需要一个 LLM 节点不需要写代码节点。缺点是输出的稳定性很难保证。不同模型、不同温度参数下模型可能会把div写成span把type: pie字段漏掉或者生成一个语法错误的 SVG。在 Demo 演示时够用但离“一键生成”还差得远。3.2 路径 BLLM 提取结构化数据 代码节点生成 SVG这是本文推荐的方案。LLM 只做它最擅长的事情理解自然语言、抽取分类和数值、输出 JSON。然后代码节点用 Python 标准库把 JSON 渲染成 SVG 字符串。优点非常明显输出格式由代码决定稳定可控SVG 是纯文本不依赖前端框架传输和渲染都方便后续扩展柱状图、折线图时只改代码节点即可LLM 节点逻辑不用大改每个节点都能调试排错成本低。缺点是代码节点需要写一点 Python对完全不写代码的人有一定门槛。不过 Dify 代码节点的 Python 语法并不复杂普通开发者在半小时内就能掌握。3.3 路径 CLLM 外部渲染服务如果不想手写 SVG也可以走外部服务LLM 节点抽取结构化数据后通过 HTTP 请求节点调用一个图表生成服务比如某个把数据渲染成图片或 PDF 的 API然后把图片 URL 返回给用户。优点是能做更复杂的图表、字体、品牌规范甚至导出高清 PNG。缺点是依赖外部服务需要维护额外的 API Key、费用和网络调用复杂度最高。3.4 三种路径对比与选型建议维度路径 ALLM 直出代码路径 BLLM SVG 代码路径 CLLM 外部服务实现成本低中高输出稳定性低高高调试难度难容易中可扩展性差好最好适合场景快速演示内部工具、生产常用对图片质量有严格要求的场景我的判断是先用路径 B 跑通再按需升级路径 C。这篇文章后面的完整实操全部基于路径 B。4. 环境准备Dify 安装与模型配置Dify 安装有两条路一条是用官方云服务版本一条是本地部署。如果你只是想快速体验“单击生成饼状图”云服务可以直接跑如果你想在企业内网或者完全掌控数据那就走 Dify 本地部署教程。4.1 方式一使用云服务版访问 Dify 官网注册账号并创建工作区进入控制台后配置模型供应商。这种方式不需要关心服务器、数据库和端口适合入门和学习。4.2 方式二Dify 本地部署Dify 本地部署最常用的是 Docker Compose 方式。这里给出通用思路具体版本请以官方仓库的最新文档为准。前提条件已安装 Docker 和 Docker Compose 插件服务器或本机至少预留 8GB 可用内存已配置好可访问的模型 API Key。基础部署流程如下git clone https://github.com/langgenius/dify.git cd dify/docker cp .env.example .env docker compose up -d执行完成后打开浏览器访问http://localhost默认会进入初始化安装页面。注意不同版本的环境变量文件和启动命令可能有差异。如果你的仓库里没有.env.example文件可以直接参考官方文档中 docker 目录下的配置说明。4.3 配置模型供应商Dify 安装完成后进入“设置 → 模型供应商”添加你使用的模型比如 OpenAI 兼容接口、DeepSeek、通义千问、智谱、Ollama 本地模型等。配置时重点检查两件事API Key 是否有权限访问你选择的模型模型名称是否填写正确。本文后续的工作流会用到 LLM 节点如果模型没有配置好后面所有节点都会报错。5. 搭建“一键生成饼状图”工作流现在进入核心实操环节。假设你已经登录 Dify并且配置好了模型供应商。5.1 创建应用并选择工作流在 Dify 工作区首页点击“创建应用”应用类型选择“工作流”。应用名称可以叫“一键生成饼状图”描述写“输入自然语言数据输出 SVG 饼图”。工作流编辑器会打开一个空白画布默认有一个“开始”节点。我们的目标是在画布上完成这条链路开始节点 → LLM 节点 → 代码节点 → 结束节点5.2 配置开始节点“开始”节点是用户请求的入口。我们需要定义一个输入变量用来接收用户输入的自然语言内容。进入开始节点配置界面新增一个输入变量名字可以叫query类型选择“文本”字段标签写“用户输入”。这样做的好处是在调试页面可以直接输入一句话比如“把 2024 年四个季度销售额画成饼图Q1 120 万Q2 150 万Q3 98 万Q4 176 万”。后续接入 API 时这个变量也是外部请求传入的入口。5.3 配置 LLM 节点让模型输出结构化数据从左侧节点库拖入一个“LLM”节点将它连接到开始节点。选择你已经配置好的模型。这里真正容易踩坑的地方是不要选择视觉能力强的多模态模型用普通文本模型就够了因为我们的目标是让模型输出 JSON 数据不是让模型“看图”。在提示词区域填入下面的系统提示词。这是整个工作流中决定输出质量的关键配置你是一个数据解析助手。用户会用自然语言提供一组分类数据和对应的数值例如“2024 年四个季度销售额Q1 120 万Q2 150 万Q3 98 万Q4 176 万”。 你的任务 1. 识别出所有分类和数据项 2. 只输出一个 JSON不允许输出任何解释性文字 3. JSON 格式必须为 {data: [{name: 分类名称, value: 数值}]} 4. 数值必须是用户输入中提取到的数字不要把“万”等单位重复换算保持用户原数字即可 5. 如果用户没有提供足够数据不得自行编造返回空数组 {data: []}。 示例输出 {data: [{name: Q1, value: 120}, {name: Q2, value: 150}]}用户输入部分选择开始节点中的query变量。这样模型每次都会拿到用户刚输入的那句话。接下来设置 LLM 节点的输出变量。我们把模型输出命名为chart_data这个变量会传给后面的代码节点。如果你不想让模型输出 Markdown 的代码块标记可以再加一句“不要输出 json 代码块标记”。但为了保险代码节点里也会做兼容处理。5.4 配置代码节点用 Python 生成 SVG 饼状图从左侧节点库拖入一个“代码”节点连接到 LLM 节点。代码节点的核心思路是读取chart_data变量解析 JSON计算各个分类的占比和扇形角度最后输出一个 SVG 字符串。这里只使用 Python 标准库不依赖 matplotlib 等第三方库因为 Dify 的代码节点运行在受限沙箱中安装第三方库并不方便。在代码节点配置中输入变量名设置为chart_data类型选择“字符串”输出变量名设置为svg、summary、error类型都是“字符串”。然后粘贴下面的 Python 代码import json import math import re def main(chart_data: str) - dict: # 清理可能出现的 Markdown 代码块标记 raw chart_data.strip() raw re.sub(r^(?:json)?\s*, , raw) raw re.sub(r\s*$, , raw).strip() try: data json.loads(raw) except Exception as e: return {svg: , summary: , error: fJSON 解析失败: {e}} items data if isinstance(data, list) else data.get(data, []) if not items: return {svg: , summary: , error: 没有可绘制的数据} # 数据清洗转 float跳过非法值 cleaned [] for item in items: try: value float(item.get(value)) name str(item.get(name)) if value 0: cleaned.append({name: name, value: value}) except (TypeError, ValueError): continue if not cleaned: return {svg: , summary: , error: 数据全部无效} total sum(item[value] for item in cleaned) if total 0: return {svg: , summary: , error: 数据总和必须大于 0} width, height 640, 460 cx, cy, r 220, 230, 150 start_angle 0.0 paths [] labels [] legends [] colors [ #5470c6, #91cc75, #fac858, #ee6666, #73c0de, #3ba272, #fc8452, #9a60b4 ] for i, item in enumerate(cleaned): name item[name] value item[value] angle value / total * 360.0 end_angle start_angle angle x1 cx r * math.cos(math.radians(start_angle)) y1 cy r * math.sin(math.radians(start_angle)) x2 cx r * math.cos(math.radians(end_angle)) y2 cy r * math.sin(math.radians(end_angle)) large_arc 1 if angle 180 else 0 path ( fM {cx} {cy} fL {x1:.2f} {y1:.2f} fA {r} {r} 0 {large_arc} 1 {x2:.2f} {y2:.2f} Z ) color colors[i % len(colors)] paths.append( fpath d{path} fill{color} stroke#fff stroke-width2/ ) mid_angle math.radians(start_angle angle / 2) px cx (r * 0.68) * math.cos(mid_angle) py cy (r * 0.68) * math.sin(mid_angle) percent value / total * 100 labels.append( ftext x{px:.2f} y{py:.2f} font-size13 fill#fff ftext-anchormiddle font-weightbold f{name} {percent:.1f}%/text ) start_angle end_angle legend_y 40 for i, item in enumerate(cleaned): color colors[i % len(colors)] legends.append( frect x480 y{legend_y} width14 height14 fill{color}/ ) legends.append( ftext x502 y{legend_y 12} font-size14 fill#333 f{item[name]}{item[value]}/text ) legend_y 28 svg ( fsvg xmlnshttp://www.w3.org/2000/svg width{width} height{height} frect width100% height100% fill#fff/ ftext x{cx} y40 font-size18 fill#333 text-anchormiddle ffont-weightbold数据占比图/text .join(paths) .join(labels) .join(legends) /svg ) summary .join( [f{item[name]} 占比 {item[value] / total * 100:.1f}% for item in cleaned] ) return {svg: svg, summary: summary, error: }这段代码有两个关键设计值得说明。第一它对 LLM 的输出做了容错。LLM 的 JSON 输出经常带 json 标记或者首尾有多余空格代码里的re.sub就是为了清理这些内容。第二它跳过了数值非法的数据项。即使 LLM 把某个数字识别成了字符串代码也能容错不会让整个工作流失败。5.5 配置结束节点并发布从节点库拖入“结束”节点连接到代码节点。结束节点需要定义返回给用户的变量。我们返回三个svg完整的 SVG 字符串summary“Q1 占比 22.1%Q2 占比 27.6%”这类文字总结error空字符串表示无错误。保存工作流后点击右上角“运行”按钮就会进入调试面板。到这里一个可以在 Dify 里运行的“一键生成饼状图”工作流已经搭好了。你可以先在调试面板里输入数据测试。6. 完整示例从自然语言到饼状图进入调试页面输入下面这句话2024 年四个季度销售额Q1 120 万Q2 150 万Q3 98 万Q4 176 万点击运行后Dify 会依次执行开始节点、LLM 节点、代码节点和结束节点。LLM 节点的输出会接近下面这种格式{data: [{name: Q1, value: 120}, {name: Q2, value: 150}, {name: Q3, value: 98}, {name: Q4, value: 176}]}代码节点拿到这串 JSON 后会生成对应的 SVG 字符串。这个字符串很长核心结构如下svg xmlnshttp://www.w3.org/2000/svg width640 height460 rect width100% height100% fill#fff/ text x220 y40 font-size18 fill#333 text-anchormiddle font-weightbold数据占比图/text path dM 220 230 L ... fill#5470c6 stroke#fff stroke-width2/ path dM 220 230 L ... fill#91cc75 stroke#fff stroke-width2/ ... /svg如果你在浏览器里打开这段 SVG可以看到一个带图例和百分比标签的饼状图。如果是在自己的前端页面里展示最简单的办法是把svg字符串通过innerHTML或v-html渲染到页面中。比如 Vue 3 项目里可以这样处理template div classchart-container div v-htmlsvgContent/div p{{ summary }}/p /div /template// 假设 res.data.outputs 是 Dify 工作流返回的结果 const result { svg: svg xmlnshttp://www.w3.org/2000/svg.../svg, summary: Q1 占比 22.1%Q2 占比 27.6%Q3 占比 18.0%Q4 占比 32.4%, error: }; export default { data() { return { svgContent: result.svg, summary: result.summary }; } };这种方式的好处是直接、不依赖图表库。如果你的项目已经使用了 ECharts 或 Ant Design Charts也可以让 LLM 节点输出 ECharts 的 option 配置再在前端用 ECharts 渲染思路是一样的。7. 运行结果与效果验证在 Dify 调试面板里工作流运行完成后每个节点的右上角都会有输入输出详情。验证是否成功按下面三步检查。第一步检查 LLM 节点输出。如果chart_data是合法的 JSON说明模型理解正确。如果输出了一段解释性文字而不是 JSON则需要调整提示词或者在代码节点里增加更宽容的数据清洗逻辑。第二步检查代码节点输出。如果svg字段以svg开头、以/svg结尾并且包含多个path标签说明代码节点执行成功。如果error字段有内容则是数据解析失败或没有有效数据。第三步核对数字。用summary字段里的百分比和原始数据进行人工比对。比如 Q4 的 176 万占总额 544 万的比例应该是 32.4% 左右如果模型输出时把数值理解错了看summary就能发现。另外要提醒一点用户输入里常见的“120 万”“98 万”会让人误以为是 1200000 和 980000但在我们的提示词里已经要求模型保留用户原数字。这样做的原因是避免单位换算错误同时也能在图例上保留用户熟悉的表达方式。8. 常见问题与排查思路在实际使用中最容易出问题的是 LLM 输出格式不稳定其次是代码节点运行环境差异。下面是一份常用排查表。问题现象可能原因排查方式解决方案LLM 节点输出不是 JSON提示词约束不够强模型输出了解释文字查看 LLM 节点原始输出在提示词中增加“只输出 JSON不得输出解释”的强约束并给一个 few-shot 示例代码节点报错 JSON 解析失败LLM 输出带有 json 代码块标记查看代码节点报错日志在代码中清理 Markdown 标记或设置提示词禁止输出代码块标记饼图里分类过多图例重叠分类超过 8 个查看cleaned数据长度在提示词或代码中限制分类数量超过阈值时提示用户简化数据总和为 0用户输入的都是 0 或负数查看代码节点error变量在提示词中要求只提取正数同时代码中跳过非正值中文显示为方框前端渲染字体不支持中文在浏览器开发者工具中查看字体替换包含中文字符集的字体或在 SVG 中引入 web 字体相同输入输出结果不稳定模型温度过高对比多次运行结果在 LLM 节点设置较低温度推荐 0.1 到 0.3图表标签重叠严重某个分类占比过小查看数据分布在代码中设置最小角度阈值小于阈值的分类合并到“其他”排查时有一个通用原则先看提示词再调代码最后改模型。不要一上来就换模型很多问题都不是模型能力不够而是没有给它一个清晰的输出格式约定。9. 生产环境最佳实践与工程建议当这个工作流从演示走向生产环境时下面这些建议会帮你省掉不少麻烦。9.1 用 few-shot 稳定 JSON 输出在提示词里给出一个“用户输入 → 期望输出”的例子效果比单纯说“请返回 JSON”要好得多。原因是 LLM 对格式的学习最容易通过示例完成。9.2 对用户输入做长度和数据量限制不要让用户无限制地输入。如果用户一次给出 50 个分类饼图会变成一团混乱。更稳妥的做法是在代码节点里做剪裁最多展示 8 个分类其余合并为“其他”。这个逻辑用 Python 写起来很简单本质上是把cleaned列表按 value 排序后截断。9.3 不要把 LLM 当计算器LLM 做语义理解很擅长但做数学计算并不可靠。在代码节点里再次校验数值总和、百分比是否接近 100%这些确定性计算必须交给代码。9.4 生产环境注意 API Key 管理Dify 应用发布成 API 后调用方需要 API Key。不要把生产环境的 Key 放在前端代码里更不要把它提交到 Git 仓库。如果需要给业务方使用建议通过后端代理转发请求在代理层做权限校验、限流和内容审计。9.5 做好日志监控与错误追踪Dify 自带一部分运行日志可以查看每个节点的输出。但在生产环境里建议在代码节点的error输出里记录更详细的错误信息并在返回结果中对用户隐藏内部堆栈避免泄露敏感信息。9.6 从饼状图扩展到更多图表这个工作流真正的价值在于“LLM 理解 确定性渲染”的组合。当你想加一个柱状图时不需要改变 LLM 节点的抽取逻辑只需要在代码节点里增加一个chart_type参数根据类型输出不同的 SVG 结构。10. 总结与后续学习方向回头看这篇文章真正重要的不是“画了一张饼图”而是理解了 Dify 工作流中的一个基础工程原则把不确定的交给大模型把确定的交给代码。Dify 的真正优势在于它让这两者可以在同一个可视化画布里组合、调试和发布这一点的价值远大于某一个具体的图表功能。读完这篇文章建议你按这个顺序继续实践先在 Dify 里把工作流跑通确认输入“把 2024 年四个季度销售额画成饼图”能返回 SVG尝试把代码节点改成输出柱状图的 SVG观察 LLM 节点是否需要改动尝试发布应用通过 API 调用一次熟悉 Dify 的发布流程和鉴权方式如果你的场景涉及企业知识库再研究一下 Dify 知识库和 RAG 功能看能不能把“数据来源”也变成自动检索。工具会变技术栈会变但“理解用大模型、输出用确定性代码”这个思路大概率会在很多应用场景里反复出现。把这条链路的每个节点都吃透你搭出来的 AI 应用才会更稳、更能上线。