Dify Chatflow 实战教程:自然语言生成 SQL 并图表展示

Dify Chatflow 实战教程:自然语言生成 SQL 并图表展示

在传统的数据查询场景中,业务人员往往需要依赖开发或数据分析人员来手动编写 SQL 查询语句。而随着大模型技术的发展,我们可以借助 Dify 实现一种更智能的方式:自然语言转 SQL(Text2SQL)。
本文将介绍如何基于 Dify 平台,快速实现一个将自然语言转换为 SQL 的智能助手,赋能业务人员快速获取所需数据。

🧱 技术架构

本教程基于以下技术组件:

模块技术
大模型GPT-4 / Claude / DeepSeek
SQL 执行自定义插件调用(rookie_text2data)
图表展示ECharts图表生成
数据来源MySQL / PostgreSQL 等任意数据库
平台Dify Chatflow(新版自动化流程)

什么是 Dify?

Dify 是一个开源的大模型应用开发平台,支持通过拖拽式配置和 API 接入,快速构建智能助手(如 RAG 检索、对话问答、SQL 生成等)。它可以让开发者更轻松地将 LLM 应用于实际业务场景中。
在这里插入图片描述

一、应用场景介绍

我们要实现的目标如下:

用户输入自然语言问题(如:“查询2023年注册的所有用户”),
Dify 自动生成对应的 SQL 语句(如 SELECT * FROM users WHERE year(register_date) = 2023)并返回。

二、准备工作

1. 安装部署 Dify(可选)

如果你还没有部署 Dify,可以选择以下方式之一:

本地部署:Dify部署/使用

SaaS 平台使用:直接注册 https://dify.ai 在线使用

准备数据库结构(Schema)

模型需要根据表结构才能正确生成 SQL。你可以通过提供 schema 描述来指导模型输出正确的语句。以下是一个示例 schema:

CREATE TABLE "ru_inspection_record_data" (
  "id" varchar(64) COLLATE "pg_catalog"."default" NOT NULL,
  "dimension_name" varchar(64) COLLATE "pg_catalog"."default",
  "system_name" varchar(255) COLLATE "pg_catalog"."default",
  "indicator_name" varchar(255) COLLATE "pg_catalog"."default",
  "longitude" varchar(255) COLLATE "pg_catalog"."default",
  "latitude" varchar(255) COLLATE "pg_catalog"."default",
  "location_address" varchar(255) COLLATE "pg_catalog"."default",
  "location_name" varchar(255) COLLATE "pg_catalog"."default",
  "community" varchar(255) COLLATE "pg_catalog"."default",
  "building" varchar(255) COLLATE "pg_catalog"."default",
  CONSTRAINT "ru_inspection_record_data_pkey" PRIMARY KEY ("id")
);

ALTER TABLE "public"."ru_inspection_record_data" 
  OWNER TO "postgres";

COMMENT ON COLUMN "public"."ru_inspection_record_data"."id" IS '主键id';

COMMENT ON COLUMN "public"."ru_inspection_record_data"."dimension_name" IS '维度名称(类型有 小区(社区),住房,街道,城区 ) 需要模糊搜索';

COMMENT ON COLUMN "public"."ru_inspection_record_data"."system_name" IS '体系名称(问题的体系) 模糊搜索';

COMMENT ON COLUMN "public"."ru_inspection_record_data"."indicator_name" IS '问题名称 模糊搜索';

COMMENT ON COLUMN "public"."ru_inspection_record_data"."longitude" IS '地址经度';

COMMENT ON COLUMN "public"."ru_inspection_record_data"."latitude" IS '地址纬度';

COMMENT ON COLUMN "public"."ru_inspection_record_data"."location_address" IS '地址名称(完整的省市区街道地址) 需要模糊搜索';

COMMENT ON COLUMN "public"."ru_inspection_record_data"."location_name" IS '区名 需要模糊搜索';

COMMENT ON COLUMN "public"."ru_inspection_record_data"."community" IS '小区名';

COMMENT ON COLUMN "public"."ru_inspection_record_data"."building" IS '楼栋';

COMMENT ON TABLE "public"."ru_inspection_record_data" IS '填报记录数据(AI表)';

三、在 Dify 中构建 Text2SQL 应用

第一步:新建一个应用

登录 Dify 控制台,点击「创建应用」。

类型选择「Chatflow」。

填写应用名称,如「自然语言转SQL助手」。
在这里插入图片描述

第二步:配置工作流

本次工作流用到了几个工具。rookie_text2data、ECharts图表生成、deepseek
在这里插入图片描述
考虑到多轮对话我们这里使用chatflow 工作流。 点击预览按钮,右下角功能开启中 我们增加对话开场白
在这里插入图片描述

第三步: 配置 LLM 节点 Prompt

配置开始节点,并在后方添加问题解析的llm大模型,实现多轮对话
在这里插入图片描述
这个地方我们定义了一个LLM大语言,模型这里我们选择了千问plus模型,系统提示词为

你是一个问题融合分析专家,负责将“记忆中的问题”和“最新的问题”融合生成一个新的问题,作为下一个对话节点的输入。你的任务是**只输出一句新的中文问句**,请严格按照以下规则执行:

任务目标:
- 你的目标是生成**一个新问题**,不是答案、解释或建议;
- 这个问题必须融合“记忆中的问题”,“记忆中的问题答案”和“最新的问题”的关键信息,围绕“最新的问题”进行优化与增强。

执行步骤:
1. **提取关键信息**:分别提取“记忆中的问题”,“记忆中的问题答案”与“最新的问题”中的关键词和关键意图。
2. **信息融合**:整合两个问题的信息和记忆中问题的答案,保证新问题语义完整、逻辑连贯。
3. **生成问句**:写出一个**语言简洁、结构规范、逻辑清晰**的中文问句,用于引导下一节点。
4. **格式校验**:确保新问题符合以下问句格式要求:
   - 必须是“一句话”,只能含一个问号;
   - 必须以“如何”、“为什么”、“是否”、“怎样”等疑问词开头;
   - 禁止输出答案、解释、建议或多句话;
   - 禁止输出 XML 标签、结构说明或多余格式;
   - 如果记忆中没有问题和答案,则直接输出:{{#sys.query#}};
   - 如果问题无需融合历史问题,保留最新问题内容即可,**但必须以问句形式重写**;

严禁输出以下内容:
- 答案(如:“用户留存率低主要是因为...”、“可以通过提升功能来...”)
- 多句说明或陈述
- 任何注释、解释、结构标签(如 `<问题>`)

示例参考:

示例1:
记忆中的问题:为什么用户活跃度下降?  
记忆中的问题答案:因为用户缺乏持续使用的激励机制
最新的问题:如何提高用户留存率?  
输出:"如何通过增强激励机制来提高用户留存率?"

示例2:
记忆中的问题:最多问题的区问题数有多少?  
记忆中的问题答案: "```echarts\n{\n  \"title\": {\n    \"left\": \"center\",\n    \"text\": \"各区域记录占比分布\"\n  },\n  \"tooltip\": {\n    \"trigger\": \"item\"\n  },\n  \"legend\": {\n    \"orient\": \"vertical\",\n    \"left\": \"left\"\n  },\n  \"series\": [\n    {\n      \"type\": \"pie\",\n      \"data\": [\n        {\n          \"value\": 55.319148936170215,\n          \"name\": \"江宁区\"\n        },\n        {\n          \"value\": 23.404255319148938,\n          \"name\": \"雨花台区\"\n        },\n        {\n          \"value\": 21.27659574468085,\n          \"name\": \"建邺区\"\n        }\n      ]\n    }\n  ]\n}\n```"
最新的问题:问题详情?  
输出:"江宁区的问题详情是哪些?"

示例3(无历史问题):
记忆中的问题:(无)  
最新的问题:当前平台的用户流失率为何上升?  
输出:{{#sys.query#}}

最终要求:
- **输出内容只能是一句话的中文问句**;
- **只能输出新问题本身,不能包含任何解释、注释或结构信息**;
- **结尾必须是问号**。

第四步:添加 SQL 生成节点

添加自然语言生成sql插件节点,并配置数据库信息,调用异常时可以添加异常分支返回报错信息.
在这里插入图片描述
随后添加代码执行节点

import re

def main(arg1: str) -> dict:
    default_output = {
        "sql": ""
    }

    try:
        # 使用正则从字符串中提取 SQL 内容
        match = re.search(r"['\"]excute_sql['\"]\s*:\s*['\"](.*?)['\"]\s*[\]}]", arg1, re.DOTALL)
        if not match:
            print("未找到 excute_sql 字段")
            return default_output

        # 原始 SQL 字符串
        raw_sql = match.group(1)

        # 替换 \n 为空格
        sql = raw_sql.replace("\\n", " ").replace("\\", "").replace("\n", " ").strip()

        # 清理 SQL 字符串中的 Markdown 语法符号(如 ```sql)
        sql = re.sub(r"```sql|```", "", sql)

        # 替换等于查询为模糊查询
        def replace_eq_with_ilike(match):
            field, value = match.group(1), match.group(2)
            if value.isdigit():
                return f"{field} = {value}"
            return f"{field} ILIKE '%{value}%'"

        sql = re.sub(r"(\w+)\s*=\s*'([^']*)'", replace_eq_with_ilike, sql)

        return {
            "sql": sql
        }

    except Exception as e:
        print(f"处理失败: {e}")
        return default_output

在这里插入图片描述

第五步:添加 SQL 执行节点

添加自然语言sql插件执行节点,并配置数据库信息,异常时可获取报错信息,并根据llm解析报错,重新执行sql生成节点
在这里插入图片描述

第六步:添加 条件分支节点

添加条件分支,判断获取到的数据是否为空,如果为空直接返回错误信息
在这里插入图片描述

第七步:添加 LLM数据处理节点

获取到data数据后需要进行数据处理,解析成我们需要的图表格式数据
在这里插入图片描述
系统提示词:

### 角色
你是一个数据分析师,需要根据上个模型生成的 SQL 及其查询结果,优先回答用户问题,回答内容不要发散,并且将关联问题的结果进行分析并以 JSON 格式返回给用户。

### 参数
- **SQL 模型生成**:{{#1745392076523.sql#}}
- **SQL 查询结果**:{{#context#}}

### 图片使用场景
- 线性图 :适用于展示趋势变化的数据,例如时间序列数据(如每月、每年的变化)。
- 柱状图 :适用于比较不同类别之间的数量或占比,例如各市的占比情况。
- 饼状图 :适用于展示整体的组成部分及其比例,通常用于单维度的比例分布。

### 要求:
1. 优先回答用户问题,回答内容不要发散。
2. 根据用户问题正确使用线性图/柱状图/饼状图。
3. 将返回内容放入到 JSON 中,格式如下:
```json
{
  "results": "用md格式先回复用户问题,其它维度数据简单概括,但是数据一定要展示出来",
  "ECHarts": "1",  // 如果需要生成图表,则为 "1";否则为 "0"
  "chartType": "线性图/柱状图/饼状图",  // 图表类型(仅当 ECHarts 为 true 时提供)
  "chartTitle": "图表标题",            // 图表标题(仅当 ECHarts 为 true 时提供)
  "chartData": "图表的数据,多个用;隔开", // 图表数据(仅当 ECHarts 为 true 时提供)
  "chartXAxis": "图表的X轴,多个用;隔开"   // 图表的X轴数据(仅当 ECHarts 为 true 时提供)
}
```/
4.json格式必须是完整的,必须是我提供的格式

#### 注意事项:
-  如果查询结果适合生成图表,ECHarts 设置为 "1",并补充 chartType、chartTitle、chartData 和 chartXAxis 字段。
- 如果查询结果不适合生成图表,则 ECHarts 设置为 "0",并省略 chartType、chartTitle、chartData 和 chartXAxis 字段。
- 咨询占比必须使用饼状图进行展示,饼状图chartData中应返回百分比。
-json必须是完整的

第八步:添加 代码执行节点

该代码执行节点为解析llm返回的图表格式信息,为后续的图表展示打前提
在这里插入图片描述
代码:

import re
import json

def main(arg1: str) -> dict:
    # 默认返回值
    default_output = {
        #"results": "",
        "ECHarts": "0",
        "chartType": "",
        "chartTitle": "",
        "chartData": "",
        "chartXAxis": ""
    }

    try:
        # 使用正则表达式提取被 ```json 和 ```包裹的内容
        match = re.search(r'```json\n([\s\S]+?)\n```', arg1)
        if not match:
            raise ValueError("输入字符串中未找到有效的 JSON 数据")

        # 提取 JSON 字符串
        json_str = match.group(1).strip()

        # 将 JSON 字符串解析为 Python 字典
        result_dict = json.loads(json_str)
    except Exception as e:
        # 如果解析失败,打印错误信息并返回默认输出
        print(f"解析失败: {e}")
        return default_output

    # 检查是否包含 ECHarts 字段
    if "ECHarts" not in result_dict:
        result_dict["ECHarts"] = "0"  # 默认设置为 "0"

    # 根据 ECHarts 的值动态检查图表相关字段
    if result_dict["ECHarts"] == "1":
        required_chart_fields = ["chartType", "chartTitle", "chartData", "chartXAxis"]
        for field in required_chart_fields:
            if field not in result_dict:
                result_dict[field] = ""  # 自动补全缺失字段为空字符串

    # 构造返回值
    return {
        #"results": str(result_dict.get("results", "")),
        "ECHarts": str(result_dict.get("ECHarts", "0")),
        "chartType": str(result_dict.get("chartType", "")),
        "chartTitle": str(result_dict.get("chartTitle", "")),
        "chartData": str(result_dict.get("chartData", "")),
        "chartXAxis": str(result_dict.get("chartXAxis", ""))
    }

第九步:添加 条件判断和图表生成节点

添加条件判断节点,判断该数据适合生成的图表格式
在这里插入图片描述
随后根据进行图表生成,柱状图,饼图相同
在这里插入图片描述
如数据不适合展示线性,饼图,柱状图则需要llm进行处理成表格数据
在这里插入图片描述
提示词:

你是一个数据处理专家,擅长将数据库返回的结构化数据转换为清晰易读的表格形式,并以 **Markdown** 格式输出。请根据以下步骤处理输入数据并生成表格展示结果:

1. **数据接收**:确认输入数据为有效的数据库查询结果(JSON、字典或二维表格式),若格式不符则拒绝处理。
2. **结构分析**:提取所有字段名(键名)和对应值,识别嵌套结构或特殊数据类型(如日期、货币)。
3. **标准化处理**:
   - 将英文字段名转换为中文(例如:`user_name` → `用户名`)
   - 修正编码问题(如UTF-8乱码)
   - 统一日期格式为`YYYY-MM-DD`
4. **表格构建**:
   - 使用 **Markdown** 表格格式,确保每条记录按“字段名: 值”形式逐行排列
   - 每个字段名和对应值使用竖线 `|` 分隔,确保列对齐
   - 嵌套数据使用2个空格缩进
   - 记录间用空行分隔
   - 数值保留2位小数(如`3.1415926` → `3.14`)
   - 长文本自动换行(每行≤80字符)
5. **表格格式要求**:
   - 使用 Markdown 表格语法:
     - 每个字段名和其对应值按列对齐展示
     - 列宽保持一致,避免出现不对齐情况
     - 使用 `|` 竖线分隔每列
     - 示例 Markdown 表格:
     | 用户名    | 手机号            |
     |-----------|---------------|
     | 张三        | 123-456-7890  |
     | 李四        | 123-456-7110  |
6. **输出要求**:
   - 输出数据必须以 **Markdown** 格式呈现,且不需要多余的解释,仅展示表格
   - 确保字段名和数据的清晰展示,避免过长的文本内容超出列宽
   - 日期、数值等特殊字段要统一格式处理
   - 必须保证输出的表格可直接复制到 Markdown 文件中,且正确渲染为表格

7. **参数**
- **SQL 模型生成**:{{#1745392076523.sql#}}
- **SQL 查询结果**:{{#1744359476252.text#}}

最终效果

在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值