
简介基于Dify与DeepSeek的智能数据分析系统设计文档面向具备Python编程和数据库基础的数据分析师、后端开发人员及数据产品经理也适合希望降低数据分析门槛、提升业务响应速度的团队。方案实现从自然语言查询、Dify工作流编排、DeepSeek模型理解到SQL自动生成、数据库执行、结果处理与可视化报表输出的全链路自动化。资源包内含1个PDF文件包体约255KB已有244人学习。文档详细讲解Dify工作流节点设计与DeepSeek模型集成方式提供完整Python代码示例、Docker容器化部署、数据库初始化脚本、SQL安全校验与性能缓存优化等企业级特性并支持Excel/PDF报告导出。读者可参照步骤在本地或测试环境搭建完整系统用于销售、客户、地域等多场景的数据查询与自动报表生成显著降低编码门槛缩短企业决策周期是构建低代码/无代码智能数据平台的实用参考资料。1. 为什么用 Dify DeepSeek 做数据分析自动化先把自然语言到 SQL 这条路走通业务人员想要一张“上个季度华东区各品类销售额”的报表开发团队排期三天真正写 SQL 只要十分钟时间全耗在沟通、改口径、等结果上。基于 Dify 与 DeepSeek 的这套智能数据分析方案本质上是把「自然语言 → SQL → 结果 → 图表 → Excel/PDF」整条链路搬进了一个可配置的工作流里Dify 负责节点编排、数据库连接和报表导出DeepSeek 负责把中文查询解析成结构化 SQL。你不需要为一个临时需求写一个查询页面业务人员直接在对话框里提问就能拿到结果。适合有一定 Python 和数据库基础的分析师、后端开发以及想在企业内部搭低代码数据平台的技术负责人。我最初以为难点在模型能不能听懂人话真正跑起来才发现Schema 给得不完整、SQL 没有安全校验才是翻车的真正原因。2. 环境准备与数据源接入模型配置、数据库连接和 Schema 抽取的一次到位2.1 依赖安装与环境变量先跑通最小集Dify 官方提供了 API 客户端DeepSeek 走 OpenAI 兼容协议数据库访问用 SQLAlchemy处理结果用 pandas最后出图用 matplotlib 和 seaborn。先把这些依赖装齐pip install dify-client deepseek-openai sqlalchemy pandas numpy pip install openpyxl reportlab matplotlib seaborn安装后不要急着写业务代码先把环境变量配置好。下面这份是本地开发最常用的一组export DIFY_API_KEYsk-your-dify-analytics-key export DEEPSEEK_API_KEYsk-your-deepseek-key export DB_TYPEmysql export DB_HOSTlocalhost export DB_PORT3306 export DB_NAMEbusiness_data export DB_USERanalytics_user export DB_PASSWORDyour-db-password注意几点DEEPSEEK_API_KEY是你在 DeepSeek 开放平台申请的格式也是sk-开头dify-client库名在 PyPI 上对应的是 Dify 官方 Python SDK不同版本WorkflowClient的初始化参数略有差异建议锁版本。环境变量里DB_TYPE目前这套代码只处理了mysql和postgresql如果你用的是 SQL Server需要自己补一个mssqlpyodbc的连接串分支。2.2 Dify 控制台数据源与 DeepSeek 模型三个位置容易配错数据库连接配置在 Dify 控制台的「数据源 → 添加数据源」里完成填主机地址、端口、库名、用户名、密码先测试连接再保存。这里有个细节Dify 工作流节点里的 Python 代码是否能直接连数据库取决于你部署 Dify 的方式——如果你用的是 Docker 部署工作流里的代码跑在独立容器里要确认容器能访问到你的数据库地址不能用localhost。DeepSeek 模型则在「模型提供商 → 添加提供商 → 自定义」里配置配置项推荐值说明提供商名称DeepSeek自定义名称可随意API 密钥sk-...从 DeepSeek 开放平台获取基础 URLhttps://api.deepseek.com/v1必须带/v1很多人在这一处漏掉模型列表deepseek-chat, deepseek-coder实际调用时用deepseek-chat更稳配模型时最常见的问题就是an error occurred during credentials validation后面避坑章节会专门讲。deepseek-coder虽然更偏代码生成但在 SQL 语句生成场景下deepseek-chat的指令跟随能力已经够用而且上下文更长。2.3 让模型认识你的表结构用 SQLAlchemy Inspector 抽取 SchemaDeepSeek 再聪明不知道你库里有哪些表、每张表有哪些字段、字段是什么类型生成的 SQL 也是瞎猜。所以工作流的第一步不是问模型而是把数据库结构抽取出来作为上下文塞给模型。这一般用 SQLAlchemy 的inspect接口完成import sqlalchemy as sa def get_database_schema(engine): 提取数据库结构信息返回表-字段、主键的映射 inspector sa.inspect(engine) schema_info {} for table_name in inspector.get_table_names(): columns [] for column in inspector.get_columns(table_name): columns.append({ name: column[name], type: str(column[type]), nullable: column[nullable], }) schema_info[table_name] { columns: columns, primary_key: [key[name] for key in inspector.get_pk_constraint(table_name).get(constrained_columns, [])] } return schema_info这段代码的核心在于inspector.get_columns()拿到的不仅是列名还包括列类型和是否可空。这些信息会让 DeepSeek 生成 SQL 时少犯两类错误一是把字符串列当数值列做聚合二是把可空列直接放进WHERE条件导致漏数据。如果你的表有注释建议一并提取出来塞进 prompt模型对“销售金额”“客户等级”这类业务名词的理解会明显更准。3. 自然语言转 SQL 工作流节点链路上的 Schema、Prompt 与 SQL 安全校验3.1 工作流节点编排五个节点按什么顺序串起来自然语言转 SQL 不是一次调用就结束而是由 Dify 工作流里五个节点串成的流水线先取 Schema再让模型生成 SQL接着做安全校验通过后执行查询最后处理结果。顺序不能乱因为下一个节点要用上一个节点的输出。workflow_config { name: 智能SQL生成工作流, description: 使用DeepSeek模型将自然语言转换为SQL查询, nodes: [ { id: schema_extractor, type: python_code, config: { code: get_schema_code, # 内部调用 get_database_schema(engine) } }, { id: deepseek_sql_generator, type: llm_chain, config: { model: deepseek-chat, prompt: sql_generation_prompt, temperature: 0.1, max_tokens: 1000, output_json: True } }, { id: sql_validator, type: python_code, config: { code: validate_sql_code, } }, { id: sql_executor, type: python_code, config: { code: execute_sql_code, } }, { id: result_processor, type: python_code, config: { code: process_result_code, } } ], connections: [ {source: schema_extractor, target: deepseek_sql_generator}, {source: deepseek_sql_generator, target: sql_validator}, {source: sql_validator, target: sql_executor}, {source: sql_executor, target: result_processor} ] }connections定义了数据流向Dify 会按照这个 DAG 串行执行。output_json: True在 Dify 里并不是一个所有版本都支持的通用参数不同版本叫response_format或json_schema你需要在部署的版本里确认——这一步如果配错了模型可能返回纯文本导致后续inputs.sql_query取不到值。3.2 DeepSeek 生成 SQL 的 Prompt把规则写进系统提示而不是靠模型自觉模型默认倾向自由发挥给它一个明确约束的 promptSQL 质量会天差地别。这段 prompt 是我用过效果比较稳定的你是专业的SQL专家请根据自然语言查询和数据库结构生成SQL语句。 数据库结构:{{database_schema}} 自然语言查询:{{input}} 请遵循以下规则生成SQL: 1. 只生成SELECT查询 2. 包含适当的WHERE条件 3. 使用正确的JOIN语句如果需要 4. 包含合理的GROUP BY和ORDER BY 5. 使用聚合函数如SUM, COUNT, AVG等 6. 确保SQL语法正确 输出JSON格式: { sql_query: 生成的SQL语句, explanation: SQL语句的解释, confidence: 置信度0-1 }注意temperature: 0.1这是文本生成任务里很低的值目的是让模型每次对同一问题给出尽量确定性的 SQL而不是发挥文采。max_tokens: 1000对一般复杂度的查询够用如果业务里常出现十几个 JOIN 的子查询建议调到 2000。另外这里的{{database_schema}}是 Dify 模板变量你需要在节点输入里把schema_extractor的输出映射到这个变量名否则模型拿不到上下文。从实际效果看prompt 里“只生成 SELECT 查询”这句话比任何安全代码都管用因为模型一旦认为你要“删除过期数据”它真的会生成DELETE。所以这个约束必须写在系统提示的最前面。3.3 SQL 安全校验的局限正则只能挡一部分还有两道兜底安全校验节点不能省。下面这段是项目里给的校验逻辑用正则过滤危险 SQLimport re def validate_sql(sql_query): # 防止SQL注入和危险操作 forbidden_patterns [ r(?i)drop\stable, r(?i)delete\sfrom, r(?i)update\s.\sset, r(?i)insert\sinto, r(?i)alter\stable, r(?i)truncate\stable, r(?i);\s*--, r(?i)union\sselect, r(?i)exec\s*\( ] for pattern in forbidden_patterns: if re.search(pattern, sql_query): return False, 包含危险SQL操作 if not sql_query.strip().lower().startswith(select): return False, 只支持SELECT查询 return True, SQL验证通过正则校验是必要的但远远不够。union select被禁止后模型可能改用子查询绕过exec(过滤了但存储过程调用不一定拦得住。所以我在生产环境会额外加两道兜底数据库账号权限收窄给 Dify 用的连接账号只授予SELECT权限从根上杜绝写入、删表。这是比正则可靠一万倍的方案。查询强制加LIMIT哪怕模型没生成执行器也会在 SQL 末尾拼接LIMIT 1000防止一次查询拖垮网络数据库。校验通过后进入执行节点执行器用 SQLAlchemy 连接数据库并把结果转成 dict 列表。结果处理器再基于 pandas 算行数、列名、数据类型以及数值列的describe()统计信息这些是下一阶段报表工作流需要的输入。4. 报表生成工作流从图表类型推荐到 Excel 导出的全自动路由4.1 先让模型选图表再用 Python 画图避免暴力 if-else拿到查询结果后下一步是决定用哪种图表展示。你当然可以写一堆 if-else 按照“时间列就画折线、类别列就画柱状”来决定但真实场景里“北京地区销售额 Top10 客户”更适合横向条形图“各品类占比”适合饼图“两个指标的关联”适合散点图。让 DeepSeek 基于结果元数据来做决策比死规则灵活得多。提示词里把查询结果的关键信息给模型根据查询结果和用户需求确定最合适的图表类型。 用户需求: {{input}} 查询结果元数据: - 行数: {{row_count}} - 列数: {{column_count}} - 列名: {{column_names}} - 数据类型: {{data_types}} 可选图表类型: - 柱状图 (bar): 比较不同类别的数值 - 折线图 (line): 显示趋势 - 饼图 (pie): 显示比例分布 - 散点图 (scatter): 显示两个变量之间的关系 - 表格 (table): 显示详细数据 - 热力图 (heatmap): 显示相关性矩阵 输出JSON格式: { chart_type: 推荐的图表类型, reasoning: 推荐理由, chart_title: 图表标题建议 }模型返回 JSON 后visualization_generator节点再用 matplotlib 绘图。这个“模型决策 代码执行”的分工比让模型直接写绘图代码稳定得多——模型不擅长精确控制坐标轴和布局但擅长判断“什么数据适合什么图”。4.2 可视化生成器的实现柱状图、折线图、饼图、散点图的分支逻辑绘图代码的核心是generate_visualization函数它根据chart_type走不同分支最后统一导出 PNG 并 Base64 编码import matplotlib.pyplot as plt import seaborn as sns import base64 from io import BytesIO import pandas as pd def generate_visualization(df, chart_type, title): plt.style.use(default) sns.set_palette(viridis) fig, ax plt.subplots(figsize(10, 6)) if chart_type bar and len(df.columns) 2: x_col df.columns[0] y_col df.columns[1] df.plot(kindbar, xx_col, yy_col, axax) ax.set_title(title) ax.tick_params(axisx, rotation45) elif chart_type line and len(df.columns) 2: x_col df.columns[0] y_col df.columns[1] df.plot(kindline, xx_col, yy_col, axax, markero) ax.set_title(title) elif chart_type pie and len(df.columns) 2: labels_col df.columns[0] values_col df.columns[1] ax.pie(df[values_col], labelsdf[labels_col], autopct%1.1f%%) ax.set_title(title) elif chart_type scatter and len(df.columns) 3: x_col df.columns[0] y_col df.columns[1] size_col df.columns[2] if len(df.columns) 3 else None if size_col and pd.api.types.is_numeric_dtype(df[size_col]): sizes (df[size_col] / df[size_col].max() * 100) 10 ax.scatter(df[x_col], df[y_col], ssizes, alpha0.6) else: ax.scatter(df[x_col], df[y_col], alpha0.6) ax.set_title(title) ax.set_xlabel(x_col) ax.set_ylabel(y_col) else: ax.axis(off) table ax.table(cellTextdf.values, colLabelsdf.columns, loccenter, cellLoccenter) table.auto_set_font_size(False) table.set_fontsize(10) table.scale(1, 1.5) ax.set_title(title) plt.tight_layout() buf BytesIO() plt.savefig(buf, formatpng, dpi300, bbox_inchestight) buf.seek(0) img_base64 base64.b64encode(buf.read()).decode(utf-8) plt.close() return fdata:image/png;base64,{img_base64}其中scatter分支是最容易出 bug 的地方如果第三列是字符串df[size_col].max()会直接抛错。所以要先判断pd.api.types.is_numeric_dtype。而bar和line分支默认取前两列如果模型推荐了 bar 但查询结果只有一列就会走进else的表格兜底。这个兜底逻辑是我特意保留的保证任何查询结果都能有输出。4.3 导出 Excel数据、分析报告、统计信息三个 Sheet报表不只是图用户往往还要能下载 Excel。这个节点用 pandas 的ExcelWriter往一个文件里写三个 Sheetdef export_to_excel(df, report_text, stats): output BytesIO() with pd.ExcelWriter(output, engineopenpyxl) as writer: df.to_excel(writer, sheet_name数据, indexFalse) report_df pd.DataFrame({分析报告: [report_text]}) report_df.to_excel(writer, sheet_name分析报告, indexFalse) if stats: stats_df pd.DataFrame.from_dict(stats, orientindex) stats_df.to_excel(writer, sheet_name统计信息) output.seek(0) excel_base64 base64.b64encode(output.read()).decode(utf-8) return excel_base64openpyxl是写 xlsx 的默认引擎注意它不能写 xls。stats是前面结果处理器算出来的describe()字典用from_dict(..., orientindex)转成行索引的表格比直接写普通 DataFrame 更适合展示多个统计量。Excel 文件最终以 Base64 字符串的形式返回给前端而不是生成临时文件——这在容器化部署里很关键因为容器随时可能被销毁临时文件是不可靠的。5. 避坑与排查部署 Dify 到跑通查询最常见的五个坑5.1 Dify 凭据校验失败an error occurred during credentials validation现象在 Dify 控制台添加 DeepSeek 自定义模型时点击保存后报an error occurred during credentials validation模型列表拉不出来。原因最常见的是 base URL 填错了。Dify 内部会请求base_url /models去校验密钥如果你只填了https://api.deepseek.com它实际请求的是https://api.deepseek.com/models而 DeepSeek 的兼容端点必须在/v1下。另外密钥复制时带回车的空格也会导致这个错误。解决把基础 URL 完整写成https://api.deepseek.com/v1并检查密钥前后没有多余字符。如果是自建的 Dify 走了 HTTP 代理还要检查 Dify 容器能否访问外网这个报错也会在代理不通时出现。5.2 文档处理报错dify unstructured api url is not configured现象在 Dify 知识库里上传 PDF 或 Word 文档提示unstructured api url is not configured for doc file processing文件一直解析不出来。原因Dify 的文档解析依赖一个叫 unstructured 的独立服务。如果你用 Docker Compose 部署 Dify 时没有把unstructured这个服务包含进去或者只部署了精简版那文档处理功能就是缺失的。解决编辑 docker-compose 文件加上unstructured服务并配置对应的 API URL 环境变量然后重启。如果只是做自然语言转 SQL不需要把业务文档灌进知识库可以直接跳过这个报错——它不影响数据库查询链路。5.3 SQL 总生成 SELECT * 并按全表扫描慢查询和高并发翻车现象业务人员问“订单总量”DeepSeek 生成了SELECT * FROM orders后端把所有字段全查出来几百万行数据直接把内存打爆连接迟迟不释放。原因模型只看到了表结构不知道表有多少行、哪些列是常用过滤条件。它没有动力去生成COUNT(*)或SUM(amount)因为 Schema 里每个字段“看起来都一样重要”。解决在 Schema 抽取时额外返回每张表的行数和索引列把这些信息拼进 prompt。同时在执行器里强制给SELECT加LIMIT 1000并把COUNT(*)类聚合查询和明细查询分流处理。我还养成了一个习惯所有 Dify 工作流里的 SQL 执行节点都设置conn.execution_options(timeout30)超时防止慢 SQL 悬挂。5.4 图表中文乱码matplotlib 默认字体不含中文现象报表生成后图表标题和坐标轴上的中文全部显示成小方块英文正常导致报表没法直接交付。原因Dify 所在的 Python 环境通常没有中文字体matplotlib 默认的 DejaVu Sans 不包含汉字。解决在绘图代码开头加上plt.rcParams[font.sans-serif] [SimHei, Noto Sans CJK SC, WenQuanYi Zen Hei] plt.rcParams[axes.unicode_minus] False如果环境里一个中文字体都没装先执行apt-get install fonts-noto-cjkDebian 系或yum install wqy-zenhei-fontsCentOS/Rocky。我会在 Dockerfile 里显式装好fonts-noto-cjk避免每次部署完再排查。5.5 并发查询把数据库连接池打满连接池参数与查询超时现象多个业务人员同时用对话式报表时Dify 工作流随机报TimeoutError: queue pool limit reached数据库端也能看到连接数飙到上限。原因SQLAlchemycreate_engine默认连接池大小是 5最大溢出 10。工作流里每个节点共享同一个 engine一次查询没释放后面的请求就排队而排队超时直接报错。解决创建 engine 时显式配置连接池参数engine create_engine( connection_string, pool_size10, max_overflow20, pool_recycle3600, pool_pre_pingTrue )pool_recycle3600让连接每隔一小时重建避免数据库端主动断开导致连接失效pool_pre_pingTrue在每次取连接时先做一次轻量探测防止拿到僵死连接。如果并发再高就把连接池参数放到配置中心按团队规模动态调整。6. 从能跑到跑稳给查询链路加缓存和批量预分析6.1 结果缓存重复的季度报表别再查库自然语言查询的特点是“同一句话会被不同的人反复问”。既然结果都一样就没有必要每次都让 DeepSeek 生成一次 SQL、再让数据库跑一遍全表。我会在 Dify 工作流最前面加一个缓存节点把用户的输入做归一化去掉空格、统一标点再计算哈希去 Redis 找结果命中就直接走报表生成不再调用模型和数据库。import hashlib import redis def get_cache_key(nl_query): normalized .join(nl_query.strip().lower().split()) return fnl2sql:{hashlib.md5(normalized.encode()).hexdigest()} def try_cache(nl_query, redis_client): key get_cache_key(nl_query) cached redis_client.get(key) if cached: return json.loads(cached) return None缓存失效策略建议用“定时 手动”双轨夜间跑批任务更新数据后手动清除相关 key对报表域直接设置 30 分钟过期保证数据不会太旧。6.2 预热常见分析维度把 Top 查询离线算好第二个技巧叫“维度预聚合”。与其让模型每次现场算GROUP BY province, category, month不如提前用物化视图或定时任务把常用的聚合结果算好。工作流执行时如果检测到查询涉及的列正好命中预聚合表就把 SQL 路由到那张几百万行压缩成几万行的表上。我在实际项目里做过一张sales_summary_daily按天存储品类、区域、客户等级的汇总数据。业务问“本月某区域销售额”时查询从 2 分钟缩短到 200 毫秒。这里的关键是让 Schema 抽取器把预聚合表也包含进去并命名得能让模型一眼看懂比如agg_sales_by_region_month。6.3 把验证脚本留成固定步骤这套系统从我第一次搭到稳定运行踩掉最多的不是模型能力而是链路里“没有验证”的环节。从那以后我每次部署都会强制走一遍先跑一条SELECT 1确认数据库连通再跑一条“明确字段”的自然语言查询确认 SQL 生成最后跑一条带ORDER BY的查询确认排序没被丢。这三条过了再开放给业务使用。希望这些步骤和坑位能帮到你让你的自然语言数据分析助手少走我走过的弯路。本文还有配套的精品资源点击获取