ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Dify+MySQL+Echarts:自然语言直查数据库并生成图表

Dify+MySQL+Echarts:自然语言直查数据库并生成图表

1. 为什么想用自然语言直接查数据库

做数据相关的工作久了,你会发现一个很尴尬的现状:业务部门要一张报表,往往不是直接跟数据打交道,而是先在聊天工具里描述需求,再由数据开发转成SQL、跑数、做图、发过去,然后业务看着图又提出新的修改意见……一来一回,少则半天,多则两三天。真正花在写SQL上的时间可能就十几分钟,其余时间全都耗在了沟通对齐上。

所以我一直想做一个工具,让业务同学能用大白话直接问数据,比如“上个月各区域的销售额排名,给我做个柱状图”,系统自动把这句话翻译成SQL,去数据库里查,再把结果渲染成图表。这个想法听起来很美好,但落地的时候会遇到几个现实的麻烦:自然语言转SQL的准确率怎么保证?查询出来的数据怎么安全地限制权限?图表怎么做到动态适配不同查询结果?

这篇文章就是记录我用Dify + 数据库 + Echarts把这条路走通的全过程。文中的方案适合有一定开发基础、想在企业内部快速搭建数据分析助手的同学参考,涉及的思路和踩坑经历比单纯看官方文档要有用得多。

2. 技术选型:为什么是Dify、MySQL、Echarts这三件套

2.1 Dify在中间层扮演的角色

选型之前我对比过几条路线。第一是纯写后端服务,自己调大模型接口做SQL生成和校验,那意味着要处理Prompt管理、多轮对话、用户权限、日志追踪一堆问题,开发量不小。第二是用开源的一体化应用框架,但很多框剪对私有化部署和数据库对接支持不算友好。最后落在Dify上,主要是看中它把“模型接入、Prompt编排、知识库、工作流、应用发布”都打包了,本身又是开源可私有部署的,跟企业内部数据打通比较顺。

Dify在整套系统里承担的是对话理解与流程编排中枢。业务用户把问题抛给Dify应用,Dify根据我配置的Prompt和流程去调用大模型,产出SQL,再通过内置的工具节点连接数据库执行查询,最后把结果整理好返回给前端。也就是说,模型调用、会话记忆、多轮追问这些逻辑都不需要自己写后端代码,Dify都管住了。

2.2 数据库选择:为什么用MySQL而不是数仓

有人可能会问,都做数据分析助手了,为什么不直接上数仓或者大数据组件?原因很实际:很多中小团队的线上业务库就是MySQL或者PostgreSQL,分析需求也集中在最近的业务明细和聚合数据上,数据量没有大到必须上数仓的程度。用MySQL可以少引入一套技术栈,而且Dify对MySQL的连接支持很成熟,读写流程简单直接。

我这里用的是MySQL 8.0。选8.0主要是它支持窗口函数,某些复杂分析SQL写起来会方便很多,另外JSON类型处理也更顺手,后面做查询参数传递时用得上。如果你的团队已经在用其他关系型数据库,比如PostgreSQL,整个方案骨架同样适用,只是方言函数有些差异需要额外兼容。

2.3 前端图表库:Echarts的不可替代性

图表部分我选的是Echarts,而不是一些轻量图表库。原因有三个:一是Echarts支持的数据可视化类型非常全,柱状图、折线图、饼图、散点图、热力图甚至地图都能覆盖,能应对业务五花八门的图表需求;二是它对动态数据的适配性好,天然接收JSON数组格式的data,做后端查询结果到前端图表的映射非常顺利;三是社区成熟,遇到问题搜解决方案很容易,企业内二次开发成本低。

3. 整体架构与核心流程拆解

3.1 系统结构总览

整套系统的运行链路大概是这样的:

业务用户(聊天窗口) ↓ 自然语言问题 Dify应用(Prompt编排 + 对话管理) ↓ 执行动作 工具节点(连接MySQL执行SQL) ↓ 查询结果(JSON) 返回给前端页面 ↓ Echarts渲染为图表

Dify这边我建了一个“对话型应用”,里面配置好系统Prompt,告诉大模型它的角色是“数据分析助理”,并给出数据库的表结构信息。用户提问后,Dify会调度大模型生成SQL,然后通过工具节点执行SQL并获取结果。

这里有个关键点:不要把SQL执行逻辑塞进Prompt里。我最初试过让模型直接返回SQL,再由前端接收后去请求后端接口执行,结果发现会话一长模型容易“忘记”前面定义好的表名和字段名,生成出来的SQL经常报错。后来改成Dify的工具节点去绑定数据库查询动作,让SQL的生成与执行在同一个流程内完成,稳定性大大提高。

3.2 用户提问到可视化的完整链路

从用户提问到图表渲染,我拆成了这样几步:

  1. 用户在对话窗口输入“上个月各区域销售额排名,柱状图展示”。
  2. Dify接收到问题,通过Prompt中预置的表结构说明,让大模型生成对应的SQL,比如“SELECT region, SUM(sales_amount) AS total_sales FROM orders WHERE order_date >= '具体日期' GROUP BY region ORDER BY total_sales DESC”。
  3. 工具节点去MySQL执行这条SQL。
  4. 查询结果以JSON数组形式返回,格式为[{"region": "华东", "total_sales": 125000}, ...]。
  5. Dify把结果和图表类型建议一起返回给前端。
  6. 前端解析出数据列名,自动匹配Echarts图表配置,渲染出柱状图。

整个过程用户感知到的就是“问一句话,图就出来了”,但背后每一环都需要打磨,尤其是SQL生成准确率和查询安全性。

4. 核心模块一:自然语言转SQL的实现细节

4.1 表结构描述是Prompt设计的核心

自然语言转SQL能不能做好,Prompt里对数据库表结构的描述方式占了七成比重。我的做法是,把每张参与分析的表的关键字段整理成结构化描述,包括字段名、字段类型、业务含义、示例值,并注明哪些字段适合做筛选条件、哪些字段适合做聚合。

举个例子,对于订单表,我在Prompt里写的是:

表名: orders - order_id: 订单ID,主键 - order_date: 下单日期,格式YYYY-MM-DD - region: 销售区域,取值范围:华东、华南、华北、西南、西北 - sales_amount: 销售额,单位元,数值类型 - category: 商品类目,如数码、家电、服饰

这样模型在生成SQL时就不容易把字段名搞错,比如把region写成area,把sales_amount写成amount。业务用词和字段命名往往不一致,Prompt里用“业务含义+字段名”的映射关系能显著降低生成错误。

4.2 多轮对话中的上下文保持

实际使用中,用户很少一句话就把需求说完整。更常见的对话是这样:

用户:“看一下华东区上个月的销售情况。” 助手:(生成查询并返回图表) 用户:“改成和华北对比。” 助手:(需要理解“对比”指的是华东和华北两个区域放到同一张图里比较)

如果Dify没有处理好会话上下文,第二次提问时模型可能不知道“改成和华北对比”中的“对比对象”是谁。我的做法是在Prompt里加入一条明确的规则:“如果用户的问题中省略了主语或对比对象,结合历史对话进行补充,并在SQL中体现完整条件。”

同时,Dify的会话记忆功能要开启,并且把消息轮次设置成最近10轮左右。太少了上下文不够,太多了容易把无关信息混进来干扰SQL生成。

4.3 生成SQL的校验机制

大模型生成SQL不可能百分百正确,直接执行出错了还好,最怕的是SQL语法对但语义错,比如查错了表、漏了筛选条件、把聚合搞错了。所以我在Dify的工作流里加了一道校验节点,规则是这样:

  1. 只允许SELECT查询语句,禁止INSERT、UPDATE、DELETE、DROP、ALTER等写操作。
  2. 查询必须有明确的LIMIT限制,防止一次拉取过量数据。
  3. 对涉及时间字段的查询,自动要求带上时间范围条件,避免扫描全表。
  4. 如果模型返回的SQL不符合上述规则,就提示它重新生成,最多重试两次。

这些校验不复杂,但能把很多低级错误挡在外面。

5. 核心模块二:数据库连接与安全管控

5.1 Dify中的数据库工具配置

Dify里配置数据库工具不算难,但在实际项目中坑不少。我的配置经验是:

配置项推荐值说明
数据库类型MySQL根据实际情况选择
主机地址内网地址不要用公网地址,降低暴露风险
端口3306默认端口
用户名专用账号不要用root,单独建只读账号
密码强密码定期更换
默认数据库业务分析库限定查询范围

单独建只读账号这个点非常重要。我给数据分析助手用的是数据库里一个只读权限的账号,只能执行SELECT,从源头上防止了模型生成意外写操作的可能。如果你的数据库是主从架构,可以连只读从库,既安全又不影响线上业务性能。

5.2 查询超时与资源控制

还有一个容易忽略的问题是查询超时。业务库的数据量一大,某些聚合SQL可能要跑很久,如果不加限制,一个慢查询就能把数据库连接池拖垮。我的处理方式是在Dify工具节点配置时,给SQL执行加了一个超时时间,比如10秒,超时就直接报错返回,通知用户缩小查询范围或增加筛选条件。

另外,所有查询尽量走索引。Prompt里我会提示模型,对于时间筛选字段和需频繁过滤的字段,应优先使用索引字段作为条件。一开始没加这个约束,模型生成的SQL经常扫描全表,有一次直接跑了一分多钟,吓得我赶紧加了查询超时。

5.3 用户权限与数据范围隔离

如果数据分析助手要给多个部门用,权限隔离就得认真对待。最简单的方式是在Prompt中根据用户身份限定数据范围,比如销售部用户只允许查自己区域的数据,在生成SQL时自动附加“WHERE region = '华东'”。

Dify本身支持用户体系,可以在对话上下文中拿到用户标识,然后在Prompt或工作流里根据用户标识动态注入数据权限条件。

6. 核心模块三:Echarts动态渲染方案

6.1 查询结果到图表配置的映射

SQL查询出来的结果是JSON数组,比如:

[ {"region": "华东", "total_sales": 125000}, {"region": "华南", "total_sales": 98000}, {"region": "华北", "total_sales": 113000} ]

前端拿到这个数组后,需要把它转成Echarts能识别的配置。通常我会让大模型在返回结果时附带上一个字段说明,比如“图表类型建议用柱状图,X轴用region,Y轴用total_sales”,前端再做映射。

我实现了一个简单的适配函数,逻辑是:优先使用大模型建议的图表类型和轴字段;如果没有建议,就按数据字段的数量和类型自动推断。字段是字符串类型的放X轴,数值类型的放Y轴,有日期字段优先用日期做X轴。

6.2 图表类型自动匹配规则

不同分析需求适合不同的图表,我做的匹配规则大致是这样:

数据分析需求推荐图表类型
对比多个类目的大小柱状图
展示随时间的变化趋势折线图
展示占比构成饼图
展示两个数值字段的关系散点图
展示排名变化排序柱状图

这个规则也写进了Prompt,让大模型在生成SQL时顺带给出图表类型建议。这里有个经验:不要完全依赖模型的判断,前端要做好兜底。比如模型推荐饼图,但查询结果有5个以上字段,饼图显示会很拥挤,这时候前端要能自动切换成横向柱状图。

6.3 前端接数据的完整示例

前端我用的是Vue3加Echarts,接数据的过程不复杂,核心代码大致是这样:

// 假设difyResponse是从Dify返回的结果 const jsonData = difyResponse.result.rows; const chartType = difyResponse.result.chartType || 'bar'; const xField = difyResponse.result.xField || Object.keys(jsonData[0])[0]; const yField = difyResponse.result.yField || Object.keys(jsonData[0]).find(key => typeof jsonData[0][key] === 'number'); const xAxisData = jsonData.map(item => item[xField]); const seriesData = jsonData.map(item => item[yField]); const option = { xAxis: { type: 'category', data: xAxisData }, yAxis: { type: 'value' }, series: [{ type: chartType, data: seriesData }] };

这样写的好处是,不论查询结果多少行、字段叫什么,前端都能动态适配,不需要针对每个图表单独写死配置。

7. 实操落地过程:从零到能用的三步走

7.1 第一步:搭建Dify环境和建立应用

Dify的部署方式主要有两种:Docker Compose部署和已有环境直接安装。我推荐Docker Compose,一套命令就能把后端服务、前端页面、数据库、向量库都拉起来,方便后期迁移和扩容。具体部署流程不细说了,官方文档很清晰,主要提醒几个细节:

  1. 部署时注意资源分配,Dify本身会占用不少内存,至少准备4G内存以上的机器。
  2. 安装完后,第一次登录会要求设置管理员账号,之后在“应用”模块创建新的对话型应用。
  3. 模型提供商配置时,选好你实际可用的大模型服务,然后填入对应的API Key。如果团队已有统一的模型网关,填网关注入的地址就行。

7.2 第二步:配置Prompt和数据库工具

这一步是整个系统的灵魂。创建好对话型应用后,我把系统Prompt设置成了这样一段话,你可以直接参考改:

你是一名资深数据分析师,你会根据用户的自然语言问题,结合提供的表结构信息,生成对应的MySQL查询语句。 注意事项: 1. 只能生成SELECT查询语句,严禁生成其他类型的SQL语句。 2. 查询结果必须限制行数,最多返回1000行。 3. 如果用户提到时间范围的描述性词语(如“上个月”“最近一周”),先换算成具体日期再生成SQL。 4. 生成SQL后,请同时给出图表类型建议(柱状图、折线图、饼图、散点图),以及X轴和Y轴对应的字段名。

然后把数据库连接信息配置进工具节点,确保Dify能实际执行SQL。这一步配置完成后,我建议先在Dify的应用调试页面里手动测试几个问题,看SQL生成和执行是否正常。

7.3 第三步:前端页面集成与联调

最后一步是把Dify应用嵌入到前端页面里。Dify提供了两种方式:一种是直接嵌入对话组件,官方封装好的WebApp组件,几行代码就能嵌进去;另一种是把应用发布后的API接口自己调用,前端完全自定义界面。

我采用的是第二种,因为我要把对话结果和Echarts图表展示结合起来,用官方WebApp组件不太好自定义渲染逻辑。调用Dify的API接口后,前端拿到对话的最终回复内容,再解析出数据数组和图表配置,交给Echarts渲染。

8. 实测效果:哪些场景表现优秀,哪些情况会翻车

8.1 表现优秀的典型场景

在我搭建好的测试环境里,有几类问题模型处理得很稳定:

  1. 带明确筛选条件的查询:“查一下3月份数码类目的销售额。”这类问题SQL生成准确率很高,因为表结构和字段映射在Prompt里写得很清楚。
  2. 分组聚合类查询:“按区域统计订单量,从高到低排序。”这类问题模型能正确识别GROUP BY和ORDER BY,而且排序方向把握得准。
  3. 时间范围换算:“查最近7天的每日新增用户数。”模型能根据当前日期自动算出具体日期范围,生成带日期条件的SQL。

8.2 容易翻车的情况和应对

翻车主要集中在几种情况:

  1. 字段含义含糊的业务词。比如“毛利”和“净利”在业务里可能是两个不同字段,但用户描述得很模糊,模型往往会猜错。应对方法是提升Prompt里字段业务含义的描述精确度,把容易混淆的字段在Prompt里用括号注清楚。
  2. 多表关联的复杂查询。涉及多张表JOIN时,模型偶尔会搞错关联条件和表别名。我的经验是尽量把常用查询逻辑固化到Prompt示例中,比如给出两个典型的JOIN查询范例,让模型模仿。
  3. 图表类型选择不当。比如用户没说图表类型,只说了“看下销售趋势”,模型有时会返回饼图,导致前端渲染效果很差。应对方法是在Prompt里加强规则:提到“趋势”“变化”时优先折线图。

8.3 我踩过的最深一个坑

必须说一个实际遇到的问题:Dify工具节点执行SQL时,如果查询结果包含非常大的数值或过长字符串,处理流程会出现响应时间变长甚至超时。我第一次上线测试时,业务同学问了一个“全部订单明细”的问题,模型生成了SELECT * FROM orders的SQL,直接查出几十万行,前端页面卡了快半分钟。

后来我在系统Prompt里加了强约束:默认情况下查询结果最多返回100行,用户明确要求更多时再放宽,但上限不超过1000行。这样既满足大部分看数场景,又不会把前端页面拖垮。

9. 提升准确率和体验的几个进阶调优

9.1 建立常用问法示例库

模型生成SQL不是靠死记硬背,而是靠理解模式。我发现如果Prompt中内置5到10个典型的“问题→SQL”示例对,准确率会明显提升。

比如Prompt中放一段:

示例1: 问题:各区域销售额排名 SQL:SELECT region, SUM(sales_amount) AS total_sales FROM orders GROUP BY region ORDER BY total_sales DESC

这种few-shot示例比单纯描述规则更管用,模型会模仿示例的写法来生成类比SQL。

9.2 引入历史反馈修正机制

还有一个实用技巧:把用户反馈纳入到流程里。在Dify应用的消息后增加一个“反馈”入口,用户可以点“这个回答正确”或“这个回答不对”。对于被标记为“不对”的历史对话,可以定期人工查看,把错因归类,再针对性调整Prompt描述或补充示例。这套机制虽然听着简朴,但实测下来对准确率提升效果最直接。

9.3 可视化结果的下钻与联动

做到能查能看之后,我还加了一个增强功能:在下发的图表上支持点击某个柱子,追问对应的明细数据。比如柱状图显示了各区域的销售额,点击“华东”那根柱子,系统自动追问“华东区的明细订单,按日期展示”。实现方式是在前端捕获Echarts的点击事件,把点击的维度值作为新问题追加到对话消息里,这样形成了一种简单的“下钻”交互。用户反馈相当好,比单纯给一张静态图好用得多。

10. 扩展思路:这个架构还能往哪些方向走

整个方案跑通稳定后,我一直在想怎么让它发挥更大价值。有几个方向值得拓展:

一是接入更多数据源。目前只接了MySQL,如果业务里有PostgreSQL、SQL Server或者大数据平台的查询引擎,可以按同样的思路接入,让一个入口统一查多处数据。

二是把报表能力做得更完整。现在主要是对话式即时查询,下一步可以加入定时任务,让Dify按固定时间把某个问题对应的图表结果自动推送到群或者邮件,这样业务每天早上打开就能看到最新数据。

三是引入数据权限的细粒度管理。现在是在Prompt层面做数据范围限定,后续可以考虑对接企业已有的SSO和权限中心,让每一个用户在Dify里的查询都自动遵守统一权限策略。

四是让可视化更智能。比如结合用户的角色和偏好,自动选择更合适的图表样式和配色;或者根据数据分布特点自动决定是否做对数轴处理、是否添加数据标注等。这些目前Echarts都支持,核心是让模型或规则层能更自动地做出决策。

回到这个项目本身,我最大的体会是:自然语言转SQL加数据可视化这件事,单看任何一个环节都不算新,但把它们用Dify的工作流和工具节点串联起来之后,等于把一个需要“开发+算法+前端”三方配合的功能,压缩到了一个能快速迭代的产品里。做类似项目时,别一上来就追求模型100%准确,先把链路跑通,再靠Prompt调优和反馈机制慢慢打磨,会是更务实的节奏。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进