Text2SQL 训练数据合成:利用大模型批量构建黄金问答对与 SQL 校验
上个季度为了优化公司内部垂直领域的 Text2SQL 表现,团队采购了一批开源基础模型打算进行微调(SFT)。训练集构建的任务分发到业务线后,动员了六位数据分析师连续手工标注了两周。结果两周后开会验收,大家差点背过气去:
六个人手工写了 800 条数据,其中有 150 条 SQL 在目标数仓上根本跑不通(字段名拼错、关键字漏写);有 200 条自然语言提问充满了严重的口语歧义(例如“看看那个商户的数据”,大模型根本不可能猜出要查什么);更可怕的是,同一种业务指标在不同分析师手下写出了三种不同的口径。几位分析师叫苦连天:“每天写业务 SQL 都写不完,还要手工捏造提问和标准答案,这简直是体力折磨!”
想要让大模型在特定企业的复杂数仓 Schema 上达到 90% 以上的开箱即用准确率,高质量的微调数据集是绝对核心。而依靠纯人工手搓训练集不仅效率极其低下,而且数据多样性(Diversity)极差。构建一套利用大模型反向合成、结合底层数据库沙箱执行双向校验的自动化黄金问答对合成流水线(Synthetic QA Pipeline),是工业级 Text2SQL 落地的必由之路。
一、 传统合成方法的致命短板
很多团队在尝试合成数据时,往往采用“单向正向生成”:直接把建表 DDL 扔给大模型,让它“请根据这个表结构生成 50 个问题和对应的 SQL”。这种粗放方式生产出来的往往是低质的“人工智障”数据:
- SQL 复杂度严重塌缩:模型极其倾向于生成简单的
SELECT * FROM table WHERE id = 1,或者仅包含单列SUM()的低幼查询,对多表嵌套子查询、开窗函数(Window Functions)、自关联以及复杂条件过滤避而不重。 - 冷僻字段无人问津:大模型有强大的名称偏好,永远只抓取
user_id、create_time、status这几个字段出题,表中真正反映核心业务逻辑的长尾字段(如某些特定履约状态码、结算分摊标志)覆盖率为 0。 - 无真实执行反馈的“逻辑幻觉”:模型生成的 SQL 在语法上看似合法,但一放到真实的数据库实例中执行,往往因为数据类型不匹配、除以零、或者连表字段值不存在而抛出异常。
二、 反向生成与双向闭环合成架构
为了生产出真正具备生产可用度的高难度训练样本,我们采用**“种子元数据拓扑 $\to$ 逆向 SQL 复杂度生成 $\to$ 反向自然语言问题反推 $\to$ 物理沙箱执行校验”**的闭环架构:
+-------------------------------------------------------------+ | 阶段 1: 数仓真实元数据拓扑 (DDL + 字段分布特征 + 外键图谱) | +-------------------------------------------------------------+ | v +-------------------------------------------------------------+ | 阶段 2: 复杂 SQL 模板与抽象树合成器 (SQL AST Synthesizer) | | - 强制注入多算子: Window Functions / GROUP BY / Multi-JOIN | | - 保证长尾业务字段的高频覆盖 | +-------------------------------------------------------------+ | (产出高难度 Candidate SQL) v +-------------------------------------------------------------+ | 阶段 3: 真实数据库执行沙箱 (Execution & Grounding Sandbox) | | - 丢入带脱敏数据的测试数据库真实跑一遍 | | - 校验: 报错即丢弃 / 返回结果为空(Empty Set)即降级调整 | +-------------------------------------------------------------+ | (产出物理验证合法的 Executable SQL) v +-------------------------------------------------------------+ | 阶段 4: 反向提问多样化扩写 (Back-Translation to Multi-NL) | | - 让大模型扮演不同角色(挑剔的运营、小白业务、严谨财务) | | - 生成多种口吻、带同义词、甚至轻度口语省略的自然语言提问 | +-------------------------------------------------------------+ | v +-------------------------------------------------------------+ | 黄金数据集落库 (Golden Dataset: Prompt / Completion SFT 对) | +-------------------------------------------------------------+三、 核心实现:自动化合成与物理校验流水线代码
以下是我们内部用于批量生产黄金问答对的核心引擎简化版。代码利用 SQLGlot 进行语法巡检,并集成 SQLite/DuckDB 内存沙箱进行真实数据落地验证:
import duckdb from typing import List, Dict, Any import json class Text2SQLDataSynthesizer: def __init__(self): # 初始化内存级验证沙箱,加载最小样例脱敏数据 self.sandbox_conn = duckdb.connect(database=':memory:') self._setup_mock_schema() def _setup_mock_schema(self): """在沙箱中构建数仓微型模型并填充真实分布特征的测试数据""" self.sandbox_conn.execute(""" CREATE TABLE fact_order ( order_id BIGINT, user_id BIGINT, amount DECIMAL(10, 2), order_status VARCHAR, created_at TIMESTAMP ); INSERT INTO fact_order VALUES (1, 101, 120.50, 'PAID', '2026-10-01 10:00:00'), (2, 102, 350.00, 'REFUNDED', '2026-10-02 11:30:00'), (3, 101, 80.00, 'PAID', '2026-10-03 15:00:00'); CREATE TABLE dim_user ( user_id BIGINT, city VARCHAR, vip_level VARCHAR ); INSERT INTO dim_user VALUES (101, '上海', 'GOLD'), (102, '北京', 'SILVER'); """) def execute_and_verify(self, candidate_sql: str) -> bool: """物理校验阶段:SQL 必须能在真实引擎上无报错执行且返回有效行""" try: result = self.sandbox_conn.execute(candidate_sql).fetchall() # 严格标准:不仅不能报错,还必须能查出非空结果,证明条件未互相冲突 if len(result) > 0: return True return False except Exception: return False def build_back_translation_prompt(self, valid_sql: str, schema_desc: str) -> str: """根据已验证正确的 SQL,让强力模型反向推导自然语言问题""" return f""" 你是一位资深业务分析专家。请仔细阅读以下在目标数仓中完全合法且能正常出参的 SQL 语句。 请针对这条 SQL,反向推导 3 种不同业务角色在日常工作中最可能提出的自然语言问题(要求句式不同、包含口语化和严谨表达)。 [目标数仓元数据] {schema_desc} [标准物理 SQL] {valid_sql} 请以纯 JSON 数组格式输出 3 个问题字符串,不要包含任何额外说明。 """ def process_candidate_pipeline(self, candidate_sql_pool: List[str], schema_desc: str) -> List[Dict[str, Any]]: golden_dataset: List[Dict[str, Any]] = [] for sql in candidate_sql_pool: # 1. 第一关:沙箱执行硬门禁 is_valid = self.execute_and_verify(sql) if not is_valid: continue # 2. 第二关:模拟调用外部大模型进行反向自然语言生成 (此处用 Mock 示例演示逻辑) # 在实际运行中,调用 GPT-4 或 Claude 3.5 Sonnet 执行反向推导 mock_nl_questions = [ "看下各城市黄金会员的累计消费金额是多少", "统计上海和北京高等级用户的真实付费表现", "分地区汇总 GOLD 级别 VIP 的订单总额" ] # 3. 产出高质量多对一黄金训练样本对 for q in mock_nl_questions: golden_dataset.append({ "instruction": "请根据给定的数据库元数据结构,将用户的自然语言分析需求精准转化为可执行的 SQL。", "input": f"元数据:\n{schema_desc}\n\n需求: {q}", "output": sql }) return golden_dataset四、 黄金数据集的高阶质检三要素
在流水线源源不断产出样本时,必须引入自动化质检过滤器(Quality Filter),杜绝脏样本进入微调流水线:
- AST 深度与复杂度打分(AST Complexity Scoring):
通过 SQLGlot 解析 SQL 的抽象语法树深度。单表简单查询得 1 分,带聚合得 2 分,多表 JOIN 得 3 分,包含开窗函数或 CTE(公用表表达式)得 5 分。在最终生成的训练集中,强制要求复杂度大于 3 分的样本占比不低于 65%,坚决防止训练集平庸化。 - 语义一致性自闭环校验(Round-Trip Verification):
合成出(NL_Question, Target_SQL)后,将该NL_Question发送给另一个零样本(Zero-Shot)的基础大模型生成一条新 SQL,并在沙箱中比对两者的执行结果集合(Result Set Equivalence)。若结果完全一致,证明该问题的语义与 SQL 达到高度一致的数学对齐。 - 同义词与负样本注入(Hard Negative Sampling):
在自然语言提问中刻意混淆指标名称(例如用“净收入”、“流水”、“销售总额”指代同一个物理字段),同时构建部分故意带有语法模糊的负样本,微调大模型在遇到未定义口径时主动向用户发起反问(Clarification),而非盲目胡乱拼凑 SQL。
五、 架构师实战心法
- 真实数据特征图谱比 DDL 更重要:如果只是给大模型看
amount DECIMAL,模型永远不知道字段可能存在负数(退款场景)。在合成引擎中,必须从真实数仓采样出各列的分位数(Percentiles)、枚举值列表和 NULL 值比例,并将其注入合成上下文,才能催生出极具业务真实感的边缘用例(Edge Cases)。 - 人工分析师的定位从“标注员”转为“质检官”:解放分析师的生产力,让他们从手工写 SQL 的苦力活中脱离出来,转为每周抽检流水线产出的 100 条低置信度黄金样本。机器合成百倍吞吐,人类专家只把守最后 1% 的精度防线。
- 拥抱增量合成应对数仓 Schema 变更:当数仓新增事实表或维表字段时,自动化流水线可在夜间自动扫描元数据变更,触发针对新字段的专项合成任务,次日清晨即可产出数百条全新样本,驱动模型持续增量学习(Continual Learning)。