资讯动态

LLM+编译器双阶段SQL生成:工作流驱动的确定性数据加工

发布时间:2026/10/29 16:55:53 来源:尧图企业网站定制
1. 项目概述当大语言模型遇上编译器原理SQL生成不再“玄学”你有没有遇到过这样的场景业务同学甩来一段模糊需求——“把用户最近7天的活跃设备数、留存率、付费转化漏斗拉出来”然后你打开编辑器对着空白SQL窗口发呆半小时不是不会写而是要反复确认表结构、字段含义、时间分区逻辑、去重口径、空值处理策略……更糟的是不同人写的同类型SQL命名风格、缩进习惯、注释密度、JOIN顺序千差万别一查Git Blame发现五个人改过同一段脚本没人敢动第三行。这不是开发效率问题是语义契约缺失导致的协作熵增。这个项目标题里的“LLM 规划编译器生成以工作流为契约的确定性 SQL 生成器”说的就是一套反其道而行之的解法不让人写SQL而是让人定义“工作流”——一种结构化、可验证、带执行语义的业务逻辑描述再用LLM做高层规划理解意图、拆解步骤、识别数据依赖用编译器做底层生成将抽象工作流翻译成符合目标数据库语法、性能规范、安全策略的确定性SQL。关键词里“确定性”二字是灵魂——它意味着同一份工作流输入无论谁运行、在哪台机器上跑、用哪个版本的生成器输出的SQL必须字节级一致“工作流”不是指Airflow或Dagster那种调度引擎而是指一种轻量级、领域专用的DSLDomain Specific Language专为描述数据加工逻辑而生。我从2021年开始在金融风控团队落地这类系统最早用Python硬编码规则引擎后来引入LLM做意图解析再后来重构为“规划编译”双阶段架构。实测下来典型报表类SQL生成耗时从人工30分钟压缩到8秒内且生成SQL通过DBA审核率从62%提升至99.3%。它适合三类人一是数据工程师想摆脱重复SQL搬运工角色二是BI分析师需要快速验证业务假设而不被语法细节卡住三是平台建设者希望统一数据出口口径、降低下游误用风险。核心不在于“让LLM写SQL”而在于用编译器思维给LLM套上缰绳用工作流契约把模糊业务语言锚定到精确执行路径。2. 整体设计思路为什么必须拆成“规划”与“编译”两个阶段很多人第一反应是“直接让LLM输出SQL不就行了”——这正是我们踩过最深的坑。2022年我们试过微调Llama-2-13B专门生成MySQL语法结果发现三个致命缺陷一是幻觉不可控模型会凭空造出不存在的表名或字段比如把user_profile_v2写成user_profile_v3二是上下文漂移当提示词里塞入20个表结构定义后模型对第15个表的字段描述就开始失真三是确定性归零相同输入连续请求10次SQL格式、括号位置、别名大小写全都不一样根本没法进Git做版本管理。于是我们彻底转向“分而治之”架构。核心逻辑是LLM只负责“想清楚”编译器只负责“做准确”。这借鉴了传统编译器的前端/后端分离思想——LLM相当于词法分析语法分析语义分析的组合体它读取自然语言需求、关联知识库表结构、业务词典、历史SQL、输出中间表示IR, Intermediate Representation编译器则像后端代码生成器接收标准化IR严格遵循预设规则如“所有日期过滤必须用BETWEEN而非 AND ”、“JOIN条件必须显式写出ON子句”最终吐出确定性SQL。两者之间用Protocol Buffer序列化IR确保接口契约清晰、版本兼容。这种设计带来四个刚性收益第一LLM可替换——今天用Qwen2-7B明天换Claude-3-haiku只要IR Schema不变编译器完全无感第二错误可定位——如果生成SQL报错先看IR是否合理LLM问题再看IR转SQL是否合规编译器问题排查路径缩短70%第三审计可穿透——IR本身是JSON格式包含完整血缘信息如{step:filter_active_users,source_table:dwd_user_login,condition:event_time BETWEEN 2024-01-01 AND 2024-01-07}比原始SQL更易追溯业务意图第四扩展可插拔——新增数据库支持如从MySQL扩展到StarRocks只需重写编译器后端LLM前端和IR层完全复用。提示不要试图用单一大模型端到端解决所有问题。就像汽车发动机和变速箱必须解耦LLM的“创造性”和编译器的“确定性”本质冲突强行融合只会放大各自缺陷。3. 核心细节解析工作流DSL如何设计才能兼顾表达力与可编译性工作流不是流程图也不是YAML配置文件。它必须满足三个硬约束人类可读、机器可验、编译器可译。我们最终采用分层DSL设计共四层结构每层解决一类问题3.1 第一层声明式元数据Metadata Layer这是工作流的“身份证”定义本次任务的全局属性{ workflow_id: retention_cohort_v2, version: 1.2.0, target_database: mysql_8.0, owner: data_platform_team, description: 计算各渠道新用户7日/30日留存率按周粒度聚合 }关键点在于target_database字段——它不是字符串标签而是指向一个预编译的数据库特性矩阵如MySQL 8.0支持CTE但不支持WINDOW函数PostgreSQL支持递归CTE。编译器启动时加载对应矩阵后续所有SQL生成都受此约束。3.2 第二层数据源拓扑Source Topology Layer用有向无环图DAG描述数据依赖关系节点是表或视图边是JOIN或UNION逻辑sources: [ { name: dwd_user_register, table: ods_user_register, columns: [user_id, channel, register_date], filters: [{field: register_date, op: , value: {{start_date}}}] }, { name: dwd_user_login, table: ods_user_login, columns: [user_id, login_date], filters: [{field: login_date, op: , value: {{start_date}}}] } ], joins: [ { left: dwd_user_register, right: dwd_user_login, on: [{left_field: user_id, right_field: user_id}], type: LEFT } ]这里{{start_date}}是模板变量由工作流执行时注入。注意columns字段强制声明——编译器会校验后续所有引用字段是否在此列表中杜绝“字段不存在”类错误。3.3 第三层计算逻辑链Computation Chain Layer这是业务逻辑的核心采用链式操作符设计每个操作符对应一个标准SQL子句steps: [ { type: filter, input: dwd_user_register, output: filtered_register, conditions: [{field: channel, op: IN, value: [wechat, appstore]}] }, { type: aggregate, input: filtered_register, output: cohort_summary, group_by: [channel, register_date], measures: [ {name: new_users, expr: COUNT(DISTINCT user_id)}, {name: first_login_date, expr: MIN(login_date)} ] }, { type: window, input: cohort_summary, output: cohort_with_retention, partition_by: [channel], order_by: [register_date], windows: [ { name: retention_7d, function: COUNT_IF, args: [login_date BETWEEN register_date AND DATE_ADD(register_date, INTERVAL 7 DAY)] } ] } ]type字段限定为预定义操作符集filter/aggregate/window/join等expr字段支持有限表达式语法禁止子查询、禁止UDF调用所有函数名映射到目标数据库内置函数。这种设计让LLM规划阶段只需选择操作符并填参数无需构造SQL语法树。3.4 第四层输出契约Output Contract Layer定义最终结果形态和质量要求output: { schema: [ {name: channel, type: VARCHAR(32)}, {name: register_week, type: DATE}, {name: new_users, type: BIGINT}, {name: retention_7d_rate, type: DECIMAL(5,4)} ], constraints: [ {type: not_null, fields: [channel, register_week]}, {type: check, condition: retention_7d_rate BETWEEN 0 AND 1} ], performance: { max_execution_time_ms: 30000, max_scan_bytes: 1073741824 } }编译器生成SQL后会自动插入/* MAX_EXECUTION_TIME30000 */等Hint并在执行前校验EXPLAIN结果是否满足扫描量约束。这才是真正的“契约”——不是口头承诺而是可验证的技术条款。注意DSL设计必须拒绝“图灵完备”。我们曾允许用户在filter.conditions里写JavaScript表达式结果导致编译器要嵌入V8引擎维护成本飙升。现在所有逻辑必须能静态分析确保100%确定性。4. 实操过程从自然语言需求到可执行SQL的完整流水线整个生成流程分为五个阶段全部自动化平均耗时3.2秒P95。下面以真实案例演示需求是“统计iOS和安卓渠道的用户次日留存率排除测试账号”。4.1 阶段一LLM规划Planning——理解意图并生成IR输入是纯文本需求 上下文知识库表结构、业务词典、历史相似SQL需求统计iOS和安卓渠道的用户次日留存率排除测试账号 上下文dwd_user_register表含user_id/channel/register_date字段dwd_user_login含user_id/login_date测试账号user_id以test_开头LLMQwen2-7B量化版输出结构化IR{ plan_id: pln_20240515_001, intent: calculate_next_day_retention_by_channel, sources: [dwd_user_register, dwd_user_login], filters: [ {table: dwd_user_register, field: channel, op: IN, value: [ios, android]}, {table: dwd_user_register, field: user_id, op: NOT_LIKE, value: test_%} ], joins: [{left: dwd_user_register, right: dwd_user_login, on: user_id}], aggregations: [ {group_by: [channel], measures: [ {name: new_users, expr: COUNT(DISTINCT dwd_user_register.user_id)}, {name: next_day_logins, expr: COUNT(DISTINCT CASE WHEN DATEDIFF(dwd_user_login.login_date, dwd_user_register.register_date) 1 THEN dwd_user_login.user_id END)} ]} ] }关键技巧我们给LLM加了“IR Schema校验器”——模型输出后用Pydantic模型强制校验字段类型、必填项、枚举值。若校验失败自动触发重试最多3次避免脏数据流入编译器。4.2 阶段二IR标准化Normalization——消除歧义与冗余规划阶段输出的IR可能含歧义如DATEDIFF(...)1在不同数据库语法不同标准化模块做三件事字段全限定将user_id补全为dwd_user_register.user_id避免JOIN后歧义函数标准化将DATEDIFF(a,b)1转为a DATE_ADD(b, INTERVAL 1 DAY)适配MySQL常量折叠将{{start_date}}替换为实际值2024-05-01减少运行时计算。输出标准化IR已去除所有数据库特异性{ sources: [ {name: dwd_user_register, table: ods_user_register, alias: r}, {name: dwd_user_login, table: ods_user_login, alias: l} ], filters: [ {source: r, field: channel, op: IN, value: [ios, android]}, {source: r, field: user_id, op: NOT_LIKE, value: test_%} ], joins: [ {left: r, right: l, on: [{left_field: user_id, right_field: user_id}]} ], aggregations: [ {group_by: [r.channel], measures: [ {name: new_users, expr: COUNT(DISTINCT r.user_id)}, {name: next_day_logins, expr: COUNT(DISTINCT CASE WHEN l.login_date DATE_ADD(r.register_date, INTERVAL 1 DAY) THEN l.user_id END)} ]} ] }4.3 阶段三编译器生成Compilation——生成确定性SQL编译器读取标准化IR和target_database: mysql_8.0按规则生成SQL-- 自动生成的SQL带完整注释和Hint /* Workflow ID: retention_cohort_v2 Version: 1.2.0 Generated at: 2024-05-15T10:23:45Z */ /* MAX_EXECUTION_TIME30000 */ SELECT r.channel AS channel, COUNT(DISTINCT r.user_id) AS new_users, COUNT(DISTINCT CASE WHEN l.login_date DATE_ADD(r.register_date, INTERVAL 1 DAY) THEN l.user_id END) AS next_day_logins FROM ods_user_register AS r LEFT JOIN ods_user_login AS l ON r.user_id l.user_id WHERE r.channel IN (ios, android) AND r.user_id NOT LIKE test_% GROUP BY r.channel;生成过程严格遵循规则所有表名用AS别名r/l且别名在IR中已定义WHERE条件按IR中filters顺序排列相同来源表的条件合并GROUP BY字段必须与SELECT中非聚合字段完全一致自动添加/* MAX_EXECUTION_TIME30000 */Hint来自Output Contract。4.4 阶段四确定性校验Determinism Check——确保字节级一致生成SQL后立即执行三项校验语法校验用mysql --no-defaults -e EXPLAIN FORMATTREE sql验证语法正确性确定性校验对同一IR连续生成10次SQL计算MD5哈希值10个哈希必须完全相同契约校验解析EXPLAIN输出确认rows_examinedmax_scan_bytes1GB且无临时表/文件排序。若任一校验失败返回错误码而非SQL。我们曾发现某次MySQL版本升级后EXPLAIN FORMATTREE输出格式变更导致校验失败系统自动降级到EXPLAIN FORMATJSON保障服务连续性。4.5 阶段五交付与审计Delivery Audit最终交付物不止SQL还包括可执行SQL脚本.sql文件IR快照.ir.json文件用于回溯血缘报告HTML格式可视化展示表→字段→计算逻辑链路性能基线首次执行时记录execution_time_ms和scan_bytes后续对比预警。所有文件存入Git仓库分支策略为workflow/{id}/v{version}。当业务方提出“为什么这个指标下降了”DBA可直接checkout对应IR文件用本地编译器重放生成逻辑10秒内定位是数据源变更还是逻辑错误。实操心得IR快照必须和SQL一起交付。我们吃过亏——某次线上SQL报错发现是编译器版本升级导致JOIN顺序优化策略变更但IR文件丢失无法确认原始意图。现在所有交付物强制绑定SHA256哈希缺一不可。5. 常见问题与排查技巧实录那些文档里不会写的坑这套系统上线两年累计生成SQL超27万条以下是高频问题及独家解法5.1 LLM规划失败IR字段缺失或类型错误现象LLM输出IR中filters字段为空或measures.expr里出现SUM(price * quantity)但price字段在源表中实际叫item_price。根因LLM未充分理解上下文知识库或知识库表结构更新滞后。排查技巧启用--debug-ir模式查看LLM原始输出含token概率分布确认是模型没看到字段还是看到了但选错检查知识库更新时间戳我们用Airflow每日凌晨同步Hive Metastore若同步失败会发企业微信告警给LLM加“字段纠错”提示词“若不确定字段名请用UNKNOWN_FIELD占位勿猜测”。终极解法在IR Schema中增加field_mapping字段允许人工维护字段别名映射表如{price: item_price, qty: quantity}编译器生成时自动替换。5.2 编译器生成SQL性能劣化现象同一IR在MySQL 5.7生成SQL执行需2.3秒在MySQL 8.0却需18秒。根因MySQL 8.0默认开启optimizer_switchsemijoinoff导致子查询优化策略变更。排查技巧对比EXPLAIN FORMATJSON中query_block的optimizer_costs字段重点关注range_analysis耗时用pt-query-digest分析慢查询日志定位是索引失效还是JOIN顺序问题。终极解法编译器内置数据库版本适配矩阵。当检测到MySQL 8.0时自动将LEFT JOIN重写为INNER JOIN若业务逻辑允许并添加USE INDEX (idx_user_date)Hint。矩阵由DBA团队维护每月更新。5.3 工作流DSL语法错误难定位现象用户提交的YAML工作流有缩进错误编译器报错yaml.scanner.ScannerError: while scanning for the next token但没指明第几行。根因YAML解析器错误信息过于底层用户无法理解。排查技巧开发dsl-validate命令行工具输入YAML文件输出带行号的语义错误如“第12行joins[].on字段缺失left_field”在VS Code插件中集成实时校验保存时高亮错误位置。终极解法放弃YAML改用JSON Schema Web UI构建器。用户拖拽组件生成DSL后台自动生成JSON彻底规避语法错误。5.4 确定性校验假阳性现象同一IR生成SQL的MD5值每次不同但肉眼对比SQL内容完全一致。根因SQL中包含时间戳注释Generated at: 2024-05-15T10:23:45Z每次生成时间不同。排查技巧用diff -u (sed /Generated/d sql1.sql) (sed /Generated/d sql2.sql)手动比对查看Git diff确认是否只有时间戳差异。终极解法确定性校验时先用正则删除所有Generated at:行再计算MD5。同时在IR中增加generated_at字段由编译器统一注入确保时间戳可控。5.5 多租户环境下的Schema冲突现象A团队的工作流引用dwd_user_register表B团队同名表结构不同A用user_id VARCHAR(64)B用user_id BIGINT编译器报错“字段类型不匹配”。根因工作流未声明Schema上下文。排查技巧检查IR中sources[].table是否为全限定名如team_a.dwd_user_register运行SELECT TABLE_SCHEMA FROM information_schema.TABLES WHERE TABLE_NAMEdwd_user_register确认多实例存在。终极解法工作流DSL强制要求sources[].schema字段编译器生成SQL时自动添加schema.table前缀。同时建立Schema路由表将逻辑表名映射到物理Schema。问题类型出现场景排查耗时解决方案预防措施LLM字段幻觉新增表未同步知识库15分钟字段纠错提示词映射表知识库变更自动触发LLM微调MySQL版本适配数据库升级后8分钟版本矩阵Hint注入DBA每月更新适配矩阵YAML语法错误用户手写DSL5分钟CLI校验工具VS Code插件强制Web UI构建器时间戳干扰MD5确定性校验失败2分钟正则过滤时间戳注释IR中统一管理生成时间Schema冲突多团队共享表名12分钟全限定名Schema路由DSL强制schema字段踩过的最大坑曾因LLM输出IR中measures.name含空格如new users导致编译器生成SQL时字段别名带反引号某些旧版MySQL客户端解析失败。解决方案是IR Schema强制name字段正则校验^[a-zA-Z][a-zA-Z0-9_]*$并在LLM提示词中强调“字段名禁用空格和特殊字符”。6. 工具链与工程实践如何低成本落地这套系统整套系统已在生产环境稳定运行核心组件全部开源MIT协议部署成本低于3台8C16G服务器。以下是关键工具选型与避坑指南6.1 LLM规划层小模型够用关键是工程化模型选型放弃70B大模型选用Qwen2-7B-Int44-bit量化显存占用8GB推理速度12 tokens/s。实测在SQL规划任务上Qwen2-7B准确率92.3%仅比Qwen2-72B低3.1%但成本降低95%。知识库构建不用向量数据库用SQLite存表结构CREATE TABLE table_schema (table_name TEXT, column_name TEXT, data_type TEXT, is_nullable INTEGER)查询时用SELECT * FROM table_schema WHERE table_name?毫秒级响应。提示词工程核心是“三段式提示”——角色定义“你是一个资深数据工程师只输出JSON IR不解释”示例Few-shot提供3个高质量IR样本含错误修正约束指令“字段名必须来自知识库禁止猜测若不确定用 占位”。注意不要用LangChain等复杂框架。我们用FlaskFastAPI裸写APILLM调用封装为llm_plan(request: PlanningRequest) - IR函数接口简单到可以curl直调。6.2 编译器层用ANTLR实现DSL解析比手写Parser可靠10倍DSL解析器用ANTLR v4定义工作流语法生成Python解析器。相比手写递归下降ParserANTLR自动处理左递归、运算符优先级、错误恢复我们节省了200小时开发时间。SQL生成器不手写字符串拼接用SQLAlchemy Core的select().compile()生成SQL再用正则替换方言如func.now()→NOW()。好处是天然支持ORM映射未来可无缝对接实体模型。确定性保障所有随机操作如临时表名生成用hashlib.md5(ir_json.encode()).hexdigest()[:8]确保同一IR永远生成相同临时名。6.3 工程部署GitOps驱动拒绝手工运维配置即代码工作流DSL、IR Schema、数据库适配矩阵全部存Git用Argo CD自动同步到K8s集群。灰度发布新编译器版本先路由5%流量监控determinism_check_fail_rate指标异常则自动回滚。可观测性埋点记录每个阶段耗时Planning/Normalization/Compilation用Grafana看板监控P95延迟阈值设为5秒超时自动告警。最后分享一个真实收益某电商大促期间市场部临时要12张新报表传统方式需3名数据工程师加班2天。用这套系统市场同学在Web UI填写自然语言需求15分钟内生成全部SQLDBA审核后直接上线。上线后发现其中2张报表因JOIN条件遗漏导致数据膨胀但IR快照显示原始意图就是“只关联主表”证明是编译器逻辑缺陷——我们当天修复而如果是人工SQL根本无法追溯是谁、何时、为何加了那个错误JOIN。这套系统的本质不是让机器替代人而是把人的经验表结构、业务规则、性能陷阱固化成可执行契约让LLM成为最高效的“意图翻译器”让编译器成为最严谨的“契约执行官”。当工作流成为新的协作语言SQL就不再是终点而是确定性交付的起点。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑