☰
基于Dify工作流的Text2SQL升级:风控策略分析场景拆解
2026/10/17 3:10:31 网站建设 项目流程

Text2SQL案例升级:基于Dify工作流的风控策略分析场景拆解

这两年Text2SQL项目很多,但大部分停留在“能查出来”的层面。真正到了风控策略分析这种场景里,业务方要的不是一张流水表,而是一段能解释“为什么波动、哪个环节出了问题、该调整什么”的分析结论。这篇文章我把原来那个功能Demo级的Text2SQL案例做了一次实打实的升级,用Dify工作流把自然语言查询、指标归因分析、风险等级判断串成一条完整链路,核心是解决三个问题:怎么让模型理解风控口径、怎么把多表查询封装成可靠动作、怎么让分析结果直接能辅助策略判断。如果你正在做类似的数据分析工具落地,尤其是金融、信贷、交易反欺诈这类对准确性和安全性都敏感的场景,这篇文章会很有参考价值。

1. 场景设定与需求拆解

1.1 风控分析师到底在焦虑什么

先说个我实际遇到的场景。某信贷平台的风控团队每周都要出一份策略复盘,里面有一堆固定问题需要回答:通过率为什么降了、拒绝率升高的主要客群是哪些、某个规则命中率的变化是否显著。这些问题背后全是SQL,而分析师每天要花大量时间在写查询、调口径、对数据上,真正用来思考策略的时间反而被压缩得厉害。

更麻烦的是,风控SQL不是简单的单表查询。比如“最近7天信用贷通过率为什么下降”,这句业务表述背后至少涉及申请表、审批流水表、策略命中明细表、用户画像表四类数据。你得先按渠道分层,再看客群结构变化,还要排除节假日因素,最后定位是规则阈值问题还是流量质量变化。这套分析逻辑如果全依赖人工写SQL,即使是熟练的分析师,一上午也只能做完两三个问题。

所以这个场景要的不是一个“能聊天查数据”的玩具,而是一个能承载业务分析逻辑、把数据转化为结论的辅助决策工具。

1.2 旧方案的三个硬伤

我们之前那版Text2SQL方案,说白了就是模型拿到用户问题,直接生成SQL,扔给数据库执行,把结果表格返回给前端。听起来很顺,实际跑起来问题很大。

第一是口径完全失控。模型生成的SQL经常把“通过率”算成“通过数/申请数”,但风控团队内部的口径是“通过数/审批完成数”,且还要剔除测试单和内部单。同一个指标,口径不同,查出来的数字完全不一样,业务方对工具很快失去信任。

第二是多表关联质量差。数据库里有几十张表,字段命名又往往不规范,模型经常join错表或者漏掉必要的过滤条件。比如查询“VIP客群逾期率”时,模型可能忘了关联客群分层表,结果把全量用户都算进去。

第三是没有归因能力。就算SQL生成正确,结果也只是一张汇总表。分析师还得自己再去看明细、做下钻、对比历史,一整套分析动作在这里断了层。说白了,旧方案只是把“写SQL”这个动作自动化了,分析本身并没有被结构化,更没有形成对决策的直接支撑。

这次升级的核心目标很明确:在Dify工作流里,把“问题理解→口径绑定→多步查询→指标归因→结论生成”整个链条编排起来,让Text2SQL不再只是生成SQL,而是变成一层可管控的分析服务。

2. 为什么用Dify工作流而不是直接调大模型API

2.1 从“功能”到“流程”的关键一步

如果你只是做一个查询Demo,直接调大模型API也能跑。但到了风控策略分析这种场景,你面对的不是一两个问题,而是一整类分析任务,它们有固定的分析路径、需要访问多个数据源、还要做不同环节的权限控制。这时候你需要一个能承载“流程”的容器,而不是一行行拼Prompt和代码。

Dify工作流的核心价值在于可视化编排。你可以把一个复杂的分析任务拆成多个节点:意图识别节点、参数抽取节点、SQL生成节点、数据脱敏节点、归因分析节点。每个节点只做一件事,节点之间通过变量传递上下文。这样做的好处是你可以针对单个节点做精细化调优,而不需要改动整个链路。

比如SQL生成这个环节,我可以单独用一个模型实例,配上风控字段字典和SQL方言约束,而意图识别用另一个模型,专注做业务问题的分类。这种拆分在直接调API的代码架构里也有,但Dify把它变成了拖拽配置就能完成的事情,后续维护成本低很多。

2.2 为什么不让模型直接连数据库

这是一个经常被忽略但极其关键的架构决策。很多人做Text2SQL时,给模型配置一个数据库连接工具让它直接执行SQL,看着很高效,但风险非常大。

风控数据库里有大量敏感字段:手机号、身份证、授信额度、逾期状态。一旦模型在生成SQL时出现条件遗漏(哪怕只是少了一个内部测试标记过滤),就会把不该暴露的数据带出来。更别说万一某个Prompt注入让模型执行了危险操作,后果不堪设想。

所以我在Dify工作流里明确了分层原则:模型只负责生成SQL,执行层由固定代码模块来完成,并且执行层的数据库账号只授予只读权限,查询范围限定在指定的视图上。工作流的“数据写入”节点仅用于连接已封装好的查询服务。

用个生活化的类比:你不会让一个实习生直接拿着公章去银行转账,而是要他先填单子,再由财务审核、主管签字,最后柜台执行。Dify工作流起到的作用,就是把这个“审核签字”的过程固定下来,让每一步都可追踪、可回溯。

3. 工作流的核心设计与实现拆解

3.1 整体架构:五层模型

整套工作流我把它分成五个环节,每个环节对应Dify里的一个或一组节点。

第一层是用户输入接收层。用户在对话框里输入自然语言问题,比如“最近7天信用贷通过率为什么下降”,同时可以选择时间范围、渠道等可选参数。这一层不做什么复杂处理,只是收口。

第二层是意图识别与参数抽取层。这里用一个模型节点,通过区分不同的问题模板来确定用户的查询意图,并且抽取查询时间窗、客群、渠道、指标等关键信息。为了让抽取更稳定,我在Prompt里给了一个“可抽取参数清单”,模型只允许输出清单内定义的参数,禁止自行扩展。

第三层是SQL生成层。这是整个Text2SQL的核心,模型读取用户意图和参数,结合系统预设的风控指标口径字典,生成符合业务规则的SQL。这层的Prompt设计我后面会专门展开讲,是整个项目调优最久的部分。

第四层是执行与脱敏层。SQL生成后经过代码节点校验,只允许SELECT语句执行,禁止多语句和写操作。执行结果会过一遍脱敏规则:手机号打码、身份证隐藏中间位、额度字段做分箱处理。这个动作可能在SQL层就处理掉,不一定等结果返回后再改。

第五层是归因分析与结论生成层。把查询结果作为上下文,再结合历史同环比数据,由模型生成分析结论,指出波动的主要原因,并按风险等级给出后续建议。

这五层加起来,工作量分配的比重大约是:Prompt设计占40%、SQL生成质量调优占30%、数据侧口径治理占20%、前端交互和测试占10%。

3.2 两类方案对比:标准模板方案 vs 结构化工具方案

在Dify工作流里落地Text2SQL,实际上有两条主流路线,我两种都试了,这里详细对比一下。

方案A是标准模板方案。它的思路是将数据库表结构信息放在系统提示词里,让模型根据问题自由生成SQL。这种方案的优点是简单直接,适合表结构少、查询模式比较固定的场景。缺点是当表多、字段多的时候,模型经常被冗余信息干扰,生成SQL的准确率肉眼可见地下降。而且每次表结构调整,都要重新调整提示词。

方案B是结构化工具方案。把每个常用的分析操作封装成一个带参数的函数,比如“查询通过率趋势”“查询拒绝客群分布”“查询策略命中明细”,每个函数预先定义好SQL模板和参数映射。模型的任务从“生成SQL”变成“选择合适的函数并填参数”,准确率大幅提升,而且执行逻辑可以复用,不需要每次都从零生成。

我强烈推荐方案B,尤其是在风控这种对准确性有极高要求的场景里。模型不具备真正的推理能力,它只是模式匹配的高手。与其让它自由发挥拼SQL,不如把分析路径固化下来,让它做选择而非创造。这在工程上也是一个更成熟稳健的选择。

3.3 方案B的具体实现细节

以“通过率归因分析”这个高频问题为例,我在工作流里定义了一个工具函数,它的参数包括:

  • 时间窗口:如近7天、近30天,或指定起止日期
  • 渠道:如APP端、H5端,可多选
  • 客群分层:如新客、存量客、VIP
  • 对比周期:如上周、上月

对应的SQL模板大概长这样:

SELECT DATE(apply_time) AS apply_date, channel, COUNT(CASE WHEN is_test = 0 AND is_internal = 0 THEN 1 END) AS valid_apply_cnt, COUNT(CASE WHEN status = 'APPROVED' AND is_test = 0 AND is_internal = 0 THEN 1 END) AS approved_cnt, COUNT(CASE WHEN status = 'APPROVED' AND is_test = 0 AND is_internal = 0 THEN 1 END) / NULLIF(COUNT(CASE WHEN status = 'COMPLETED' AND is_test = 0 AND is_internal = 0 THEN 1 END), 0) AS pass_rate FROM apply_records WHERE apply_time BETWEEN :start_date AND :end_date GROUP BY apply_date, channel ORDER BY apply_date

这个模板里需要注意几个关键细节:剔除测试单和内部单的条件在每一个统计分支里都写全了,避免遗漏;“通过率”的分母用的是审批完成数,而不是粗放的申请数,这是和业务团队反复确认过的口径;NULLIF的用法防止除零报错。

模型层要做的事情,是在用户说了“最近7天”时,自动计算出实际日期区间填入参数。这个日期计算在Dify里可以直接用代码节点实现,也可以让模型在工具调用前先做一轮日期参数归一化,把“最近一周”“上周一到今天”这类表达统一成标准起止日期。

4. Prompt设计实战:这是Text2SQL质量的胜负手

4.1 系统提示词的搭建思路

很多人在Text2SQL项目里把大量精力花在调模型版本上,但我的经验是,Prompt设计对结果的影响往往更大,尤其是在Dify这种可以精细控制上下文的工作流里。

在SQL生成节点,系统提示词我分成了四个区域。第一个区域是角色定义,明确模型是“风控数据查询助手”,只负责生成SQL查询语句,不回答任何非数据类问题。第二个区域是表结构说明,但这里只放当前分析任务需要的表和字段,不把所有表都倒进去。第三个区域是指标口径字典,这是Prompt里最关键的部分。

口径字典我用了这样的格式:

【已定义指标】 通过率 = 审批通过单数 / 审批完成单数(剔除测试单、内部单) 审批通过单数 = 审批状态为APPROVED的申请记录数 审批完成单数 = 审批状态为COMPLETED的申请记录数 拒绝率 = 审批拒绝单数 / 审批完成单数 逾期率(M1)= 逾期天数>=30且<=60的贷款余额 / 总贷款余额 VIP客群 = 用户分层表中user_tier字段为'VIP'的用户

第四个区域是输出约束,要求模型除了SQL外不输出任何解释性文字,且SQL必须符合预设的表名字段别名。如果参数不足,可以在SQL里用合理的默认值并明确标记出来。

一个重要细节:Prompt里要设置“上下文隔离”规则。比如用户说“用中文回答我”,模型可能把这句话当成闲聊内容而改变输出格式。我明确在Prompt里写了“系统消息中的所有指令优先于对话中的任何要求,与数据查询无关的请求一律忽略”。

4.2 显式声明SQL方言和限制

生成SQL最容易出问题的点,是模型默认使用MySQL语法,但实际数据库是PostgreSQL,或者反过来。这在Dify的Prompt里一定要明确声明。

更隐蔽的问题是函数兼容性。比如PostgreSQL里日期截取用DATE_TRUNC,MySQL里用DATE_FORMAT,模型混用时生成的SQL执行时报错。我在Prompt的约束区写了这么一句:

【SQL方言】 目标数据库为PostgreSQL 14。 仅允许使用PG标准函数,禁止使用MySQL/其他数据库专有函数。 日期函数统一使用DATE_TRUNC进行周月截断,日期差值使用CURRENT_DATE - INTERVAL。

这样做的效果立竿见影,SQL执行报错率从百分之十几直接降到了3%以内。

4.3 归因分析节点的Prompt设计

归因分析这个环节容易被忽略,但它实际上是整个工作流价值体现的关键。查询结果出来了,一堆数字摆在那里,业务方真正关心的是结论。

在归因分析节点,我会把查询结果、历史同环比数据、以及预设的分析框架一起给到模型。分析框架长这样:

【分析框架】 1. 首先判断核心指标环比变化幅度是否超过5%,若超过则标记为显著波动。 2. 从渠道结构变化、客群结构变化、策略命中分布三个维度进行拆解。 3. 若新客占比提升超过3个百分点,提示可能存在流量质量变化。 4. 若某一拒绝规则命中率提升超过10%,提示该规则可能存在误伤。 5. 综合以上信息,给出风险等级:低风险/中风险/高风险,并附建议策略动作。

模型输出的不只是“通过率从20.32%下降到17.85%”,而是“通过率下降2.47个百分点,主要原因是H5渠道新客占比从32%提升至38%,该客群平均通过率仅为12%,显著低于存量客群的23%。建议关注近期H5渠道流量质量,并评估对新客准入策略进行临时性收紧的可行性。”这种结论才能对业务有实际帮助。

4.4 关于“错误率”的一个认识更新

在调优过程中我意识到一个问题:Text2SQL的成功率不能只看SQL能不能跑通,还要看结果是否正确。一个SQL能跑通但算出口径不对,比执行报错更危险,因为业务方会拿着错误数字做决策。

所以我在工作流里加了一个“口径校验”步骤。在SQL执行之前,先经过一个简单的规则引擎检查:SQL中是否包含“通过率”的指标计算,它的分母分子是否和口径字典一致;如果查询涉及“通过率”但SQL里没有剔除is_test和is_internal条件,就判定为不合格,直接拦截并让模型重新生成。这个步骤用Dify的条件分支节点和代码节点就能实现,不需要引入额外系统。

5. 工作流编排中的几个关键执行细节

5.1 变量传递与上下文管理

Dify工作流里的变量传递是容易被新手忽略的坑。节点之间的变量名如果定义不当,很容易出现数据断流或者串值。

我的习惯是给每个关键节点设置独立的变量前缀。意图识别节点输出统一命名为intent_result_json,SQL生成节点输出统一命名为sql_generated,执行节点输出统一命名为query_result。这样工作流的可视化界面上一眼就能看出数据流向,排查问题也方便。

5.2 条件分支与错误重试机制

实际使用中,用户提的问题千奇百怪。有的问题描述太模糊,模型抽取不出必要参数;有的问题超出了预设的分析路径,比如“把用户表导出来”这种数据导出的请求。

我在工作流里设置了三个分支:标准分析路径、参数不完整时的澄清路径、超出范围时的拒绝路径。这比让模型硬着头皮生成SQL要安全得多。如果参数不完整,工作流会触发一轮追问,让用户补充时间范围或渠道;如果是超出范围的请求,直接礼貌回绝,说明当前只支持策略分析类问题。

这个设计在实际部署时非常有用,大幅提升了用户侧的体验和信任度。毕竟谁也不想用着用着突然看到一句“抱歉,我无法回答这个问题”但不知道后面该怎么办。

5.3 缓存与性能优化

风控分析师经常会问大量相似问题,比如不同日期范围的同一类指标。如果每次都重新走一遍SQL生成和数据库查询,既浪费算力又消耗时间。

我在Dify里加了查询缓存层,以“时间段+渠道+客群+指标”作为缓存键,命中缓存时直接返回上一次的结果并标注数据生成时间。需要说明的是,这种缓存方案适合日级别批处理分析的场景,如果你做的是实时风控那套数据进来了要立刻出结果,缓存就不适用了。这个取舍要结合业务场景来判断。

6. 数据安全与权限控制:风控场景的生命线

6.1 从数据库账号层开始就“锁死”

之前提到过只读账号隔离,这不仅是技术问题,更是合规底线。我把数据库账号权限分成了三级:查询只读账号能访问的聚合视图、执行服务账号能访问指定表结构的受限视图、管理员账号只留给DBA。Dify工作流里配置的连接信息全部指向最低权限账号,保证即使是模型生成的SQL有越权部分也无法访问到敏感字段。

此外,所有SQL执行都要求指定查询超时时间,默认设置为30秒,超过后自动终止。这可以防止模型生成的SQL因为join了过多表而拖垮数据库。这也是从一次线上事故中学到的教训,之前有过一个SQL直接全表扫描,把生产库的CPU打到了80%。

6.2 敏感字段动态脱敏

即使是只读查询,风控数据库里仍然有敏感字段。我通过在工作流中配置一个脱敏代码节点,在结果返回给前端之前处理数据。

具体做法有两种场景:字段本身是敏感类型时,在SQL查询时就做脱敏处理;另一种是需要原始字段进行后续计算但最终结果不能暴露明文时,先查出来再脱敏。比如手机号,在SQL里用CONCAT(LEFT(mobile,3), '****', RIGHT(mobile,4))直接截断,计算的原数据经过了遮蔽,但还能保持可读性。身份证号类似,保留前1位后1位,其余打码。

这些脱敏规则不写在SQL生成节点的Prompt里,而是放在执行节点的固定代码中。这么做是为了防止模型生成的SQL绕过脱敏逻辑,也避免业务侧自行变更规则导致数据泄露。

6.3 生产环境与测试环境的隔离

在Dify里我把开发和生产的API连接配置分开,测试环境连接的是脱敏后的影子库,生产环境连接的是只读真实库。中间通过环境变量切换。

这条我自己踩过坑:有一次在测试环境调好SQL后直接切到生产环境,发现生产库的表结构和测试库有细微差别,导致SQL字段名报错。从那以后,每次发布前我都会跑一遍“生产环境字段校验脚本”,把SQL里出现的所有字段名和生产库的实际列名做一次比对,确保不会出现低级错误。

7. 常见问题排查与避坑清单

7.1 问题一:模型生成的SQL条件遗漏导致数据异常

这是Text2SQL最常见的问题,没有之一。模型生成了SQL,看上去很完整,但少了关键的过滤条件,比如没有剔除内部测试单,导致通过率虚高。

我的排查方法是在执行节点前加日志记录,把所有生成的SQL和参数流水都记录下来。一旦业务方反馈结果异常,我可以直接翻日志看当时SQL的上下文,快速定位是模型理解偏了还是参数抽取漏了。同时,SQL校验规则引擎做了兜底,让这类问题在前期就被拦截下来。

7.2 问题二:两个表JOIN时的性能瓶颈

风控分析离不开多表JOIN,特别是用户画像表、申请记录表、策略规则表之间的关联。但JOIN多了性能下降明显,尤其是在按天分组聚合且数据量大的时候。

我的经验是做“预聚合处理”而不是在每次查询时都实时JOIN。把常用的分析维度(渠道、客群、策略命中状态)按天做汇总,存储在宽表里,查询时直接读宽表即可。这样模型的SQL在生成时也不涉及复杂JOIN,只需要做条件筛选和按维度分组即可。准确率提升了,速度也更快。

7.3 问题三:模型在Agent模式下重复自愈但不收敛

Dify里如果配置了Agent节点,模型遇到SQL执行错误时会尝试自己修改SQL并重新执行。这个功能有时候是双刃剑。

有一次模型生成SQL查询某天的数据时,把一个日期格式化函数的参数写错了,然后它自己反复尝试修改,连续修改了七八次都没有成功,期间还把另外几个字段名也改错了,整个分析链路卡了很久。

给Agent节点的自愈轮次设置上限,最多重试2次。超过后直接进入兜底流程,返回“暂时无法完成分析,请联系管理员”。不要指望模型总能自愈,适当的止损比无限重试更重要。

7.4 问题四:用户输入的日期表达无法被正确解析

用户会说“上个月”“最近7天”“从上周一到今天”“六月的数据”,这些表达不统一。模型有时候能算对,有时候会算错。

最终我用了一个两步方案:先在意图识别层增加一个日期归一化代码节点,把已知的时间表达转换成标准日期范围;转换不了的再让模型根据当前日期推算,同时必须输出推算过程的关键节点。实测下来,日期归一化环节模型做错的比例很低,之后再把标准化日期传给SQL生成,大幅降低了因为参数错误导致的SQL错误。

7.5 问题五:金额字段被当成字符串处理

很多风控数据库里金额字段类型不统一,有的是decimal,有的是varchar。模型生成SQL时如果不注意,会直接做字符串拼接或者排序错乱。

在SQL校验规则里加一条:涉及金额字段的查询,必须用CAST转换成decimal类型后再计算。数值排序和加减操作都基于转换后的类型来做。同时口径字典里明确了金额单位是“元”还是“万元”,模型在读取时不会因为单位不一致而算错结论。

8. 经验复盘:做Text2SQL应用的几个认知转变

整套方案从设计到上线,前后迭代了好几版。回看整个过程,有几个认知层面的转变很重要,分享给大家。

第一个转变是从追求“SQL生成正确率”到追求“分析结论的正确率”。前者是工程视角,后者是业务视角。一个SQL正确但结论没有价值的工具,在业务方眼里和没有差不多。所以工作流的终点不该是数据表,而是带着判断的分析结论。

第二个转变是从“越多信息越好”到“越少上下文越好”。之前为了让模型生成更准确的SQL,我恨不得把所有表结构都塞进Prompt。结果模型反而更容易混淆。后来只放当前分析路径需要的字段和口径,效果反而提升。做Text2SQL,Prompt不是海纳百川,而是精准聚焦。

第三个转变是把错误拦截前置。第一次上线的时候,工作流跑了一个星期,业务方反馈的异常问题让我疲于应付。后来我花了一个下午,把所有能预见的异常情况都写进校验规则和分支逻辑里,后续故障率降了一大截。前置防御比事后救火重要得多。

最后聊聊这套方案的扩展空间。目前跑的是风控策略分析,但底层这套“意图识别-参数抽取-SQL生成-执行脱敏-归因分析”的架构完全可以复制到运营分析、财务分析等领域。只要把口径字典和SQL模板换成目标业务的,整个工作流就能快速复用。我把这个项目回看了一遍,最大的感触是:Text2SQL本身不是目的,把业务分析能力沉淀成可复用的工作流,才是这个方向真正的价值所在。

如果你也在做类似的项目,建议先从业务方最痛的问题切入,不用一上来就追求面面俱到。挑两个高频分析场景,把口径、SQL模板、归因框架都打磨到极致,再逐步扩展。这样用户才能看到效果,项目才有继续迭代下去的底气。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询