
1. 内容整体设计与思路拆解1.1 为什么要把SQLBot的输出转成JSON聊这个之前先说说我在Dify里做数据分析类工作流时遇到的痛点。SQLBot这个工具本身不算复杂它的职责就是接收自然语言问题、转成SQL语句、在数据库里执行、再把查询结果返回给上层流程。问题恰恰出在最后一步——返回结果的格式。数据库查询出来的东西默认是带列名、带类型的表结构直接返回给下游节点尤其是大模型或者HTTP请求节点时字段解析乱七八糟经常出现“模型看不懂这个结构”“JSON里套了个字符串”之类的弱智报错。举个真实例子。我给一个电商团队搭过一套经营分析工作流SQLBot查出来的订单数据长这样columns: [order_id, customer_name, total_amount, created_at] rows: [[A1001, 张三, 199.0, 2025-06-01 10:22:31]]这种结构如果直接喂给大模型让它生成销售报告模型倒是能勉强读但如果你要让结果通过HTTP节点推送到企业微信、钉钉机器人或者写入另一个系统的API就麻烦了。外部接口通常只认标准的JSON格式比如[ { order_id: A1001, customer_name: 张三, total_amount: 199.0, created_at: 2025-06-01 10:22:31 } ]所以把SQLBot的输出转成标准JSON本身就是工作流里一道必不可少的“数据整形”工序。谁的整形做得干净谁的后续流程就顺畅。这跟做饭一样菜切得好不好看直接决定出锅摆盘的效果。1.2 方案选型Dify工作流里处理JSON的几种路径在Dify里把SQLBot输出转成JSON我最常用的是三条路。先列个对比再展开讲。方案实现方式适合场景优缺点代码节点Python在Dify的代码执行节点里写一段Python动态解析columns和rows需要灵活控制字段类型、筛除空值时优先选择灵活度最高但需要写代码如果是纯线上版本要确认是否开放代码节点权限Jina/JQ类工具节点或自定义工具通过自定义工具调用外部JSON处理服务或借助内置的代码节点模拟不想写复杂逻辑只想做简单结构转换配置成本高依赖外部工具稳定性实际用得不多大模型节点提示词强制输出用LLM节点在提示词里要求“只输出JSON数组”数据量小100条、字段简单、允许损失精度简单粗暴但大模型偶尔会篡改数据或乱加字段有风险我个人90%的场景走代码节点方案。原因很简单SQLBot的输出格式虽然固定但字段值里藏着各种坑——NULL值、时间戳格式不一致、金额字段偶尔变字符串、甚至有换行符混在里面。只有自己写Python逻辑才能把这些脏数据逐一处理干净。大模型节点方案适合临时验证但别在生产流程里依赖它。外部工具方案除非团队里已有现成服务否则引入一个额外依赖多一层故障风险。2. 核心细节解析与实操要点2.1 SQLBot输出的原始结构到底是什么样要写解析代码首先得摸清SQLBot返回的数据结构。根据我在Dify社区版1.x和云端版的实测SQLBot节点的输出变量通常是result里面的结构类似下面这样{ columns: [id, name, value], rows: [ [1, 苹果, 100], [2, 香蕉, 200] ] }注意几个细节columns是数组里面每个元素是列名类型是字符串。rows是二维数组每一行对应一条记录元素顺序和columns一一对应。所有值默认都是字符串或者数字具体取决于数据库驱动。MySQL驱动返回的数值列可能是字符串比如100而不是100这是最容易踩的坑。如果查询结果为空rows是空数组[]但columns可能仍然有值也可能为空数组取决于SQLBot的实现版本。理解了这些写解析脚本就有方向了。核心思路是把columns和rows做zip组合把一行数据变成一个dict再把所有dict组成一个list最后json.dumps序列化。但事情没这么简单。真实生产环境里我遇到过三种异常情况rows里某一行比columns多一个字段比如某些数据库驱动会把额外的元信息塞进来。列名重复比如SELECT a.id, b.id FROM...此时columns会出现两个id直接转dict会覆盖。数值精度问题比如9988.1000001这种直接透传给大模型模型会把它原样读出来报给老板看就尴尬了。所以解析逻辑里必须带一层“防御性编程”。2.2 代码节点里写解析Python脚本的完整版我贴一段我一直在用的脚本直接复制进Dify代码节点就能跑。这段代码我迭代了四个版本踩过不少坑写出来的都是实打实能用的东西。import json def main(sqlbot_output: str) - dict: # 兼容两种情况SQLBot节点直接传dict或者传JSON字符串 if isinstance(sqlbot_output, str): data json.loads(sqlbot_output) else: data sqlbot_output columns data.get(columns) or [] rows data.get(rows) or [] # 防御去重列名重复的在后面加序号 col_map {} deduped_columns [] for col in columns: if col in col_map: col_map[col] 1 deduped_columns.append(f{col}_{col_map[col]}) else: col_map[col] 0 deduped_columns.append(col) # 防御处理列数多于实际值的情况 result [] for row in rows: item {} max_len min(len(deduped_columns), len(row)) for i in range(max_len): value row[i] # 处理NULL值转成None而不是字符串None if isinstance(value, str) and value.lower() in (null, none, nan): value None # 尝试把纯数字字符串转成int/float但保留科学计数法的原样 if isinstance(value, str): try: if value.isdigit(): value int(value) else: # 避免误转日期字符串 clean_val value.replace(,, ).replace( , ) if clean_val.replace(., ).replace(-, ).isdigit(): value float(value) except (ValueError, TypeError): pass item[deduped_columns[i]] value result.append(item) # 如果还有剩余的列rows不足时补None if len(deduped_columns) max_len: for i in range(max_len, len(deduped_columns)): item[deduped_columns[i]] None return { json_output: json.dumps(result, ensure_asciiFalse), data_list: result }这段代码做了几件事自动识别输入是dict还是JSON字符串。Dify不同版本的代码节点输入类型不统一有的节点上游会把它序列化成字符串传过来。这个兼容逻辑省了不少排查时间。列名去重。两张表联查时列名重复太常见了不去重的话后面第二个同名字段会把第一个覆盖掉数据直接丢了。尝试把数字字符串转成int/float。这是给下游减少麻烦的关键。你要是不转大模型读取100时有时会当成字符串拼接出现100200这种弱智结果。保留ensure_asciiFalse让中文正常显示。这个参数忘掉的话中文字段会变成\u5f20\u4e09日志里根本没法看传给HTTP接口也容易出编码问题。2.3 如果不想写代码用大模型节点怎么兜底有朋友说“我团队里没有会Python的能不能不写代码”可以但你得接受大模型的不可控性。我在一个临时项目里用过这招大模型节点配置如下系统提示词你是一个数据格式转换助手。用户会给你一段SQLBot的查询结果包含columns和rows。请将结果转换为JSON数组格式数组内每个元素是一个对象键为列名值为对应行数据。只输出JSON不要任何解释。用户提示词放SQLBot输出变量{{#context#}}实测下来的问题有三个数据量一上来超过50行大模型偶尔会截断输出JSON解析直接失败。数字精度会丢。199.0可能被转成19910000.50可能变成10000.5对报表场景来说还好对财务对账场景就是灾难。列名偶尔会被“优化”。模型觉得customer_name太长擅自改成name下游如果有字段映射就全乱套了。所以大模型节点方案我定位为“应急可用、生产慎用”。如果你的流程里下游只是让大模型自己读数据做总结无所谓JSON严不严格那可以用。但凡涉及接口推送、文件导出、二次计算规规矩矩写代码才是正路。3. 实操过程与核心环节实现3.1 在Dify中搭建一个完整的数据查询与JSON输出工作流说完了方案我直接演示一条我从零搭的工作流包含SQLBot查询、Python解析、HTTP推送三个环节。这是一个非常典型的“数据查询→格式转换→外部系统对接”场景。工作流节点顺序如下开始节点输入一个问题字段SQLBot节点配置数据库连接、查询模板代码节点运行上面的Python脚本HTTP请求节点把JSON结果POST到目标系统结束节点返回格式化后的结果给前端SQLBot节点的配置要点我挨个讲数据库连接Dify支持MySQL、PostgreSQL、SQL Server等主流数据库。连接串务必用只读账号防止SQL注入或者误操作写坏数据。我吃过一次亏用管理员账号配置SQLBot结果测试SQL时不小心执行了DELETE语句差点把一张测试表清空。从那以后生产环境一律走只读账号。查询模板SQLBot支持在SQL模板里用变量。写一个简单的示例SELECT id, name, amount, created_at FROM orders WHERE order_date {{#input.date#}} LIMIT 100输出变量名默认叫result我建议改名成sql_result避免跟后面的变量冲突也方便在代码节点里引用。低代码模式部分Dify版本支持直接用自然语言让SQLBot自动生成SQL。这个能力个人觉得适合快速验证但不适合生产。因为SQLBot自动生成的SQL偶尔会漏掉时间边界条件或者把INNER JOIN写成LEFT JOIN跟你预期的不完全一致。我还是习惯手写SQL模板稳。配置完SQLBot下一步是代码节点。代码节点的输入变量设置如下输入变量名: sqlbot_output 变量值: {{#sql_result#}}注意选对引用路径。Dify工作流里引用上游节点输出时格式是{{#节点名#}}如果你的SQLBot节点叫“sqlbot_1”那就是{{#sqlbot_1.result#}}。这块写错的话运行时会报变量找不到排查起来想骂人。我的经验是在Dify的画布上鼠标悬停到SQLBot节点点了“输出”面板看准确路径再填别凭记忆敲。HTTP请求节点的配置也不难把代码节点输出的json_output作为请求体请求方法: POST 请求URL: https://your-api.example.com/webhook/orders/sync 请求体: {{#code.json_output#}} 请求头: Content-Type: application/json这样一条链路就通了用户提问 → SQLBot查库 → Python解析成标准JSON → 推送外部系统。3.2 结合阿里云或其他云数据库时的特别注意事项搜一下“sqlbot aliyun”相关的关键词你会发现在云数据库环境下SQLBot的配置逻辑略有不同。这里我把实际踩过的坑列一遍。SSL证书验证失败。连接阿里云RDS MySQL时如果数据库开启了SSL强制加密Dify的SQLBot节点默认用的数据库驱动可能不认证书报SSL connection error。解决方法是在数据库连接串里加上ssl_disabledTrue或者useSSLfalse参数。注意这只是内部测试环境的做法生产环境建议把CA证书配到Dify容器里或者选用支持SSL的驱动版本。Dify社区版在Docker部署时证书路径通常需要挂载到容器内。白名单。阿里云RDS默认只允许白名单IP访问。你本地部署Dify时容器的出口IP跟你电脑的公网IP不是同一个经常出现“连接超时”的诡异问题。处理方案是把Dify服务器所在的弹性公网IP加到RDS白名单里。如果你是内网部署那就把RDS的内网地址换成Dify容器所在VPC的IP段。时区问题。阿里云的RDS实例时区可能是UTC8但Dify容器里的默认时区是UTC。SQLBot直接执行SELECT NOW()时返回的时间跟实际差8小时。这个坑不会报错但数据会错隐蔽得让人抓狂。我的建议是在数据库连接串里加上serverTimezoneAsia/Shanghai或者在SQL模板里统一用DATE_FORMAT指定格式比如DATE_FORMAT(created_at, %Y-%m-%d %H:%i:%s)。网络超时。云数据库偶尔会有连接池满了、慢查询卡住的情况。SQLBot节点默认查询超时时间可能只有30秒我建议在高级设置里调大一点或者给查询SQL加个MAX_EXECUTION_TIME提示MySQL 5.7支持比如SELECT /* MAX_EXECUTION_TIME(5000) */ id, name FROM orders WHERE ...这是让数据库在5秒内跑不完就直接放弃而不是干等着把工作流拖死。3.3 代码输出JSON后怎么在Dify里调试和验证很多朋友代码写完了点运行发现节点报错也不清楚问题出在哪。这里分享一个调试习惯先看原始输出再判断解析逻辑对不对。在代码节点前面加一个临时的“文本处理”节点或者直接用日志节点把SQLBot的result原样打印出来。确认它是dict还是字符串确认columns和rows里有没有奇怪的值。我常用一招在代码节点里故意return原始数据工作流跑一次然后去看节点输出详情点“查看中间结果”就能拿到SQLBot的原始数据结构。确认完原始结构再跑解析逻辑。如果解析出错重点看报错信息里提到的字段位置比如KeyError: rows那就是你拿错了变量名或者SQLBot的版本结构变了。还有一个笨办法但非常好用直接把SQLBot的结果复制到本地Python环境里跑一遍解析脚本本地调通了再贴回Dify。这样调试速度比在Dify里反复点运行快得多。4. 常见问题与排查技巧实录4.1 问题速查表SQLBot输出转JSON的典型报错我把这几个月在Dify社区和自己的工作流里遇到的高频问题整理成一个速查表每条都附上解决方案方便你直接对着查。症状可能原因排查思路与处理KeyError: rowsSQLBot节点输出变量名设错或者版本差异导致结果结构不同检查代码节点引用的是不是{{#sqlbot_1.result#}}在SQLBot节点的输出面板里确认字段名json.dumps报Object of type Decimal is not JSON serializable数据库返回的数值类型是Decimaljson模块默认不能序列化在代码里加一个自定义转换函数或者先把Decimal转成float再序列化中文变\u5f20\u4e09调用json.dumps时没有加ensure_asciiFalse加上ensure_asciiFalse输出就是正常中文推送外部接口时报格式错误JSON里混入了NULL、NAN等特殊值在解析代码里统一把None值转为null或者根据接口要求改为空字符串SQLBot执行超时查询SQL跑太慢或者连接池满了优化SQL加索引用MAX_EXECUTION_TIME限制调大SQLBot超时时间数据库连接失败白名单、SSL证书、网络时区等多个原因先测试数据库本机连接再检查白名单再看连接串参数字段顺序错乱columns和rows元素对不上SQL里用了SELECT *统一用显式字段名不要SELECT *解析代码里按索引配对4.2 我踩过的最坑的一个问题Decimal序列化刚才表格里提到Decimal序列化这个坑我印象太深了。有一次我从PostgreSQL里查出来一堆金额字段Python代码节点里直接json.dumps(result)然后程序就报错Object of type Decimal is not JSON serializable。当时我第一反应是Dify版本问题查了半天才发现是数据类型的问题。解决方案里我最推荐的是在解析脚本里加一个default参数把不可序列化的类型统一转掉def default_serializer(obj): if hasattr(obj, item): # 兼容numpy类型 return obj.item() if hasattr(obj, isoformat): # 兼容datetime return obj.isoformat() if hasattr(obj, quantize): # Decimal return float(obj) raise TypeError(fType not serializable: {type(obj)}) # 调用时 json.dumps(result, ensure_asciiFalse, defaultdefault_serializer)这样无论是Decimal、datetime还是numpy的float64、int64都能被安全序列化。这个函数我直接放在公用代码片段里每次新建代码节点就粘一份省得反复踩坑。4.3 空结果和NULL值怎么处理不报错SQLBot查不出数据时rows是空的。但你的流程不一定应该报错——有时候空结果本身就是有效信息比如“今天没有新订单”。我在解析脚本里加了两道保险空结果时返回一个空数组[]而不是返回{error: no data}。这样下游HTTP节点会推一个[]出去对接方收到空数组能正常处理不会触发误报警。如果数据库字段本身是NULL字符串解析时转成None。但注意有些接口不接受None只能接受空字符串这时候在代码最后统一遍历一遍把None替换成。我在一次对接企业微信机器人时遇到过这个问题null值直接被推送对面Webhook直接拒绝了请求改成空字符串才通过。另外一个细节SQLBot在一次性查询多行时如果某行某个字段是NULLrows里对应的位置可能是None也可能是字符串None不同驱动表现不同。所以解析脚本里我做了一层字符串判断value.lower() in (null, none, nan)时转成None。这招真的好用算是多个驱动的“统一口径”。4.4 工作流上下文超长时的处理新版Dify里代码节点输出很大的JSON时可能导致工作流上下文超长。我遇到过一种情况SQLBot返回了500行数据解析成JSON后接近200KB直接塞给后续的大模型节点结果报“上下文超长”错误。处理办法有几种在SQL里做聚合让数据库先把数据压缩好再返回。比如只需要每日总额就别查明细直接GROUP BY DATE(created_at)。在解析代码里做字段裁剪只要下游需要的列其他字段全删掉。这个思路最实用明细数据转成汇总数据后体积能减少80%。如果确实需要传全量数据就别走大模型节点改用HTTP节点推给外部存储服务。5. 扩展让SQLBot的JSON输出更好用的三个习惯5.1 统一输出Schema下游接口不打架我在团队里定过一个规矩所有SQLBot查询只要往外部系统推数据输出JSON的Schema必须提前约定好。比如时间字段统一ISO8601格式2025-06-01T10:22:31金额字段统一保留两位小数ID字段统一转字符串。这样外部系统对接时不用每接一个工作流就重写一次解析逻辑。做法是在代码节点里增加一个“schema_config”参数用字典定义每个字段的期望类型和格式然后循环处理。代码量会增加一些但维护成本是实打实地降低。5.2 把解析逻辑沉淀成可复用的工具函数Dify内置的代码节点没有函数库的概念但你可以在团队里维护一个“常用代码块”文档里面放上面提到的default_serializer、列名去重逻辑、NULL值清理逻辑。每次新建代码节点直接复制粘贴改一下输入输出变量名就行。我甚至试过用Dify的自定义工具功能把这段Python逻辑封装成一个HTTP服务所有工作流都调它。好处是改动一处全局生效不用把每个代码节点都翻出来改一遍。缺点是引入服务部署成本如果只是零星几个工作流在用反而没必要。5.3 定时任务场景下的JSON输出落地除了实时查询我还拿这套方案跑过定时任务。Dify支持定时触发工作流我配置了一个每天早上9点跑的任务SQLBot查昨天的销售汇总Python解析成JSONHTTP推送到公司内部的数据看板系统。这个任务稳定跑了两个多月中间只出过一次问题——数据库迁移后某张表的字段从Decimal变成了字符串解析脚本没报错但推出去的数据让看板系统显示异常。排查那次问题的过程特别能说明一个道理格式转换这种事永远不要高枕无忧。数据库结构一变你的解析逻辑就得跟着适配。所以我后来在代码节点里加了一层字段校验检查关键字段的类型是不是预期类型如果意外变化就在返回值里加一个format_warning字段推送前人工介入检查。最后分享一个小技巧回到最开始的话题SQLBot输出转JSON这件事听着简单但真正做扎实需要把网上那些“能跑就行”的代码好好打磨一遍。我这个版本用了这么久最大的心得是解析逻辑里所有防御性处理都不是多余的。列名去重、NULL归一化、数字类型转换、序列化兜底每一行代码背后都是某个深夜踩出来的坑。如果你在Dify里搭类似的工作流建议拿我这套代码先试跑一次看看自己的SQLBot输出结构是否完全一致。尤其是不同数据库驱动之间差异很大一定要用自己的真实数据做验证不要拿示例数据判断结果。等跑顺了再根据下游需求做字段裁剪和格式调整。这样一轮下来你基本就能把这套逻辑吃透再遇到什么SQLBot输出的奇形怪状数据都不会慌了。