第34章 NL2SQL 工程化¶
场景引入¶
第33章把“上周华东销售下滑的主要 SKU 是哪些”绑定到 gmv_ops@2025Q1、region_code = EAST、grain = sku 和当前用户的 sales_ops View。本章的问题是:在这些约束下,DataAgent 如何生成 SQL,并保证它可执行、可审计、可修复。如果把 NL2SQL 简化为“模型写 SQL”,系统很快会遇到生产问题。模型可能漏掉 tenant_id,可能引用不在 Linked Schema 中的表,可能用错 SKU 字段,可能生成一个扫描全库的 Join,也可能在 SQL 正确执行后给出没有口径说明的结论。DataAgent 需要的是一条工程流水线,不是一段漂亮的 SQL 文本。
生产事故通常不会以“SQL 语法错误”的方式出现。更常见的情况是 SQL 能跑,图表也能展示,但结果悄悄偏离了业务规则。一个模型把 order_amount 当作销售额,没有使用语义层定义的 amount_ops;另一个模型忘记追加 tenant_id,幸好执行器拦了下来;还有一次,模型为了回答“主要 SKU 是哪些”,生成了一个跨两年订单明细的宽 Join,差点把在线 OLAP 拖慢。对业务用户来说,这些都不该被解释成“模型偶尔不稳定”。它们说明 NL2SQL 没有被放进受控执行链路。
第33章已经把口径和权限交给语义层,本章继续往下走。Linked Schema 只是起点,系统还要把它编译成 Semantic SQL 或结构化查询对象,再根据数据库方言生成可执行 SQL。执行前,SQL 要经过语法、只读、Schema、成本和 Policy 校验;执行后,结果要被摘要、截断、写入 artifact,并连同 metric_id@version、数据新鲜度和 SQL hash 一起进入 Trace。错误也要结构化返回 Planner,让系统能修复列名、方言函数或排序问题,同时拒绝越权和高成本查询。这一章不追求模型一次写出最漂亮的 SQL,而是让 SQL 成为可治理的中间产物。用户最终需要的是可信回答,平台需要的是可回放证据,数据团队需要的是可定位问题。NL2SQL 在 DataAgent 中承担的责任,是在这些约束之间把自然语言意图转成受控查询,避免模型绕过语义层和执行器直接访问数据库。本章把 NL2SQL 放回 Agent 平台中理解。Planner 负责提出下一步动作,语义层负责口径和 Join,Registry 负责工具调用审计,sql_executor 负责只读校验和执行,Observation 把错误结构化返回 Planner。用户最终看到的是业务解释,SQL 只是中间产物。
34.1 NL2SQL 在 DataAgent Run 中的职责¶
DataAgent 的 NL2SQL 位于 Planner 循环中。Planner 拿到 Question Frame 和 Linked Schema 后,可以调用语义层编译查询,也可以让 Gateway 在约束范围内生成或微调 SQL。执行阶段,SQL 必须经过 Registry 中的 sql_executor。如果模型或 Planner 直接连接数据库,语义层、Policy 和审计链路都会被绕开。
图34-1:NL2SQL 协作时序。来源:本书自绘。Alt text:时序图展示 Planner、语义层、Registry、sql_executor、模型网关之间的调用顺序,从 Linked Schema 编译到 SQL 执行、Observation 反馈、结果解释。
一次正常调用可以拆成四段。第一段是语义编译,把 Metric、Dimension、filter 和 time range 转成 Semantic SQL 或结构化查询对象。第二段是 SQL 生成,在语义层约束下补充排序、分组、Limit 或数据库方言。第三段是执行治理,sql_executor 做只读校验、权限校验、成本评估和执行。第四段是业务解释,Planner 基于结果、指标上下文和可信上下文生成回答。Semantic SQL 在本书中指由语义层决定聚合口径、Join 和默认过滤的 SQL 或查询对象。Planner 可以调整展示相关的细节,例如 ORDER BY 和 LIMIT,gmv_ops 的公式则由语义层固定。运营总监看到的是语义层定义的运营 GMV,不是模型临时 SUM(amount) 得出的数字。这条边界要在接口上体现。语义层可以返回可执行 SQL,也可以返回结构化查询对象;无论哪种形式,Metric 聚合、默认过滤和 Join 路径都应被标记为不可改写区域。Planner 如果需要扩展查询,应通过语义层 API 申请新增维度或过滤。直接编辑聚合表达式会让 SQL 自修复变成口径漂移。
34.2 从 Linked Schema 到 SQL¶
继续沿用“华东下滑”的案例,Linker 在这一阶段已经把用户问题绑定到指标、维度、时间范围和权限约束。后面的 NL2SQL 才能在这个约束面里展开,而非直接面对一条模糊自然语言问题。
{
"metrics": [{"metric_id": "gmv_ops", "version": "2025Q1", "title": "运营 GMV"}],
"dimensions": ["region_code", "sku"],
"filters": [{"field": "region_code", "op": "eq", "value": "EAST"}],
"time_range": {"start": "2025-06-09", "end": "2025-06-15", "grain": "week"},
"compare_to": {"start": "2025-06-02", "end": "2025-06-08"},
"view": "sales_ops",
"tenant_id": "demo-tenant"
}
语义层编译时会注入默认过滤、时间维表、Join 路径和 Metric 聚合逻辑。tenant_id 和行级权限由执行层或 Policy 强制追加;把它们交给模型记忆,遗漏一次就可能造成越权。一个简化后的 SQL 可能如下。
SELECT
o.sku_id AS sku,
SUM(CASE WHEN o.order_week = '2025-W24' THEN o.amount_ops ELSE 0 END) AS gmv_last_week,
SUM(CASE WHEN o.order_week = '2025-W23' THEN o.amount_ops ELSE 0 END) AS gmv_prior_week
FROM analytics.orders_fact AS o
WHERE o.tenant_id = 'demo-tenant'
AND o.region_code = 'EAST'
AND o.order_week IN ('2025-W23', '2025-W24')
AND o.is_internal = false
GROUP BY o.sku_id
ORDER BY (gmv_last_week - gmv_prior_week) ASC
LIMIT 50;
这段 SQL 要先看来源,再看语法。amount_ops 来自 gmv_ops 的定义,is_internal = false 来自 Metric 默认过滤,时间范围来自 Question Frame,tenant_id 来自 IAM 和 Policy。模型如果自己写出类似 SQL,也必须经过同样校验。在大 schema 中,生成前还要剪枝。Prompt 中只应出现本问相关的少量表和列;塞入全库 DDL 会抬高上下文成本,也会让模型把无关表拉进查询。CHESS 这类工作强调先检索、再选择 schema、再生成、再校验,背后的原则与本书一致:大库 NL2SQL 必须先缩小上下文,再让模型写 SQL。剪枝结果也要进入审计。一次查询用了哪些表、哪些列、哪些候选被排除,决定了模型能生成什么 SQL。若用户质疑“为什么没有查退货表”,平台应能说明当前 Question Frame 没有包含退货分析,或当前 View 不允许访问退货明细。没有剪枝记录,错误 SQL 很难定位是 Linking 错、剪枝错,还是模型生成错。
34.3 生成路线选择¶
NL2SQL 工程不只有一种路线。企业可以根据历史 SQL、schema 规模、私有化要求和延迟预算选择组合方案。
表34-1:常见 SQL 生成路线。来源:本书整理。
| 路线 | 做法 | 适用场景 |
|---|---|---|
| 示例驱动 | 检索相似历史问句和 SQL 作为示例 | 已积累大量问句和 SQL |
| 分步拆解 | 先 Linking,再分类,再生成,再修复 | schema 复杂、需要可解释 |
| 大库剪枝 | 先缩小表列范围,再生成和校验 | 上千列、跨主题数据仓 |
| 开源模型专训 | 用 SQL 语料训练或微调模型 | 私有化和成本敏感 |
华东案例规模不大,语义层编译加轻量 Gateway 微调就足够。若进入全集团数据仓,单个 View 仍可能包含大量表和列,这时需要更重的剪枝流水线。若企业不能把 schema 和问题发送给闭源模型,可以在第45章 LLM 网关中接入本地 SQL 专用模型。选择路线时要避免两种极端。一种是所有问题都用一个巨大 Prompt,让模型自己解决口径、字段、Join 和安全;另一种是把流程拆得过细,导致一次简单问数经过十几个模型调用。早期可以从语义层编译优先开始,把错误样本沉淀下来,再决定是否引入更复杂的流水线。
错误样本沉淀后,可以逐步升级生成路线。重复出现列名错误,说明 Linked Schema 或字段别名需要补强;重复出现 Join 错误,说明语义层 Join 图或编译器需要改;重复出现复杂归因问题写不出 SQL,说明应转入第35章的 Python 分析。继续逼模型写更复杂的 SQL,只会把分析任务伪装成查询任务。NL2SQL 的改进要回到语义层、Linker、执行器和评测集,而不能只改 Prompt。历史 SQL 示例也要谨慎使用。相似问题可以提供好示例,但历史 SQL 可能包含旧 Metric 版本、旧字段名或过宽权限。检索示例后,应先检查 metric_id@version、View、租户范围和方言,再放入模型上下文。否则示例驱动会把历史技术债复制到新 Run。方言也是路线选择的一部分。DuckDB、Postgres、Trino、Snowflake、BigQuery 在日期函数、窗口函数、Limit、JSON 字段和标识符转义上都有差异。语义层或执行器应明确传入 dialect,而非让模型自行猜测。SQL 生成、AST 解析、EXPLAIN 和执行都要使用同一方言配置,否则本地校验通过、线上执行失败会很常见。缓存可以降低延迟,前提是不破坏审计。语义层编译结果、Schema 剪枝结果、历史示例检索结果都可以缓存;最终 SQL 结果也可以按租户、Metric 版本、时间范围和权限上下文缓存。缓存 key 中必须包含 metric_id@version、View、tenant、过滤条件和数据新鲜度,否则用户可能拿到旧口径或其他权限域的结果。
34.4 执行前校验与自修复¶
SQL 触达 OLAP 之前,至少要经过四类校验。语法校验确认方言合法;只读校验拒绝 DDL、DML、导出和写操作;Schema 校验确认表列属于 Linked Schema 和 View;成本校验用 EXPLAIN、分区条件和行数阈值防止失控查询。Policy 还要确认 tenant_id、行级权限和字段脱敏。
表34-2:SQL 校验层次。来源:本书整理。
| 校验层 | 主要问题 | 失败处理 |
|---|---|---|
| 语法 | 方言不合法、列引用错误 | 返回结构化错误给 Planner |
| 只读 | DDL、DML、导出、写操作 | 直接拒绝,不重试 |
| Schema | 表列不在 Linked Schema 中 | 回退 Linking 或修正 SQL |
| 成本 | 缺少分区、Join 过宽 | 拒跑或要求缩小范围 |
| Policy | 越权、缺少租户过滤 | 拒答并审计 |
自修复只适合可修复错误。例如列名 sku 写错为 sku_id,Planner 可以根据 Observation 和 Linked Schema 重试。越权错误、敏感字段访问和缺少权限要直接停止;如果继续让模型反复尝试,安全边界就会被当作普通报错处理。
{
"status": "error",
"code": "TOOL_EXECUTION_ERROR",
"message": "column \"sku\" not found",
"hint": "linked_columns contains sku_id, not sku",
"sql_hash": "a3f8c2",
"retry_count": 1,
"max_sql_retries": 3
}
重试次数要独立于 Run 的 max_steps 管理。max_sql_retries 用完后,Run 应进入 failed,或在配置允许时进入人工修复。不要让 Planner 在错误 SQL 上无限循环,也不要把失败隐藏成“暂时没有数据”。Observation 的设计会直接影响自修复质量。给 Planner 的错误如果只有数据库原始报错,模型很难判断该改列名、方言函数还是查询范围。错误对象还应包含可用的修复线索,例如候选列、允许表、当前方言、失败阶段和是否允许重试。给用户展示的错误则应更克制,避免泄露表结构或权限细节。一个错误对象可以同时服务 Planner、用户和审计,但字段要分层。
自修复还要防止口径漂移。Planner 修 SQL 时,只能改列名、别名、方言函数、排序或 Limit 这类技术细节;如果修复需要改变 Metric、扩大 View 或去掉默认 filter,就应回到第33章重新 Linking 或进入人工确认。SQL 能跑不代表口径仍然正确。失败分类应进入评测体系。语法错误、列名错误、权限错误、超时、空结果和解释错误,对应不同修复路径。把它们都归为“SQL 失败”,团队只会继续调 Prompt;把类别分开后,才能知道应改 Linker、语义层、执行器还是回答模板。第39章的 DataAgent Eval 会继续使用这些错误标签。执行前审查还应处理时间语义。“上周”“本季度”“去年同期”应由 Question Frame 和时间维表统一展开,而非交给模型临时写日期字符串。不同企业的周起始日、财务月和节假日调整都可能不同。时间范围一旦由模型自由生成,SQL 看似正确,业务含义却可能偏差一周。
34.5 只读执行与资源保护¶
sql_executor 的默认姿态应是保守。允许只读 SELECT、只读 CTE、窗口函数和有限的 UNION ALL,禁止 DDL、DML、SELECT INTO、文件导出和外部函数。执行账号应是只读账号,最好连接只读副本。资源保护同样重要。每次查询要有语句超时、最大行数、最大字节数、并发限制和租户级 QPS。结果集过大时,返回 sample、统计摘要和 artifact 引用,不要把所有行塞进模型上下文。Planner 需要的是解释所需的信息,审计需要的是结果引用和 SQL hash,两者不必都进入 Prompt。权限与脱敏分两层处理。sql_executor 负责执行基线,例如只读、超时、禁表、必须有租户谓词。企业 Policy 负责行级权限、列级脱敏和角色范围。二者串联生效。即使 SQL 看起来只读,只要用户没有当前 View 的权限,也应拒绝执行。
allow_statements: [SELECT]
max_rows: 10000
max_bytes: 5MB
statement_timeout_ms: 30000
require_tenant_predicate: true
deny_tables: [raw_pii, admin]
执行保护要基于 SQL parser 和 AST,而非字符串匹配。系统需要判断语句类型、表引用、函数调用和子查询。WITH 子句里也可能包含写操作或危险表达式,只看开头关键词就放行会留下绕过空间。成本保护在 DataAgent 中尤其重要。业务用户的自然语言问题通常不会主动限定分区、行数或 Join 范围,模型也可能为了回答“为什么下降”生成宽表查询。执行器应在 EXPLAIN 阶段估算扫描行数、Join 宽度和结果大小,超过阈值时让 Planner 缩小时间范围、降低粒度或改走离线任务。一个临时问数不该拖垮生产 OLAP。结果保护同样需要设计。Top-K 结果可以直接进入模型上下文,完整结果应保存为 artifact,只传递引用、schema、样例和 hash。报告或后续 Python 分析需要完整数据时,由受控工具读取 artifact,不要让模型持有全部明细。这样可以控制 token,也能降低敏感数据泄露风险。
审计记录至少应包含 SQL hash、规范化 SQL、参数、用户、租户、Metric 版本、View、执行时间、扫描量、返回行数和结果 artifact。对外展示不需要这么多字段,但内部回放和成本治理需要它们。第38章的 Trace 会继续使用这些信息。限流策略要按租户和任务类型区分。普通问数可以走在线 OLAP,复杂诊断或宽表导出应进入异步任务或离线队列。高优先级用户并不意味着可以绕过资源保护,只能拥有不同的队列优先级或更高配额。否则 DataAgent 会把自然语言入口变成绕过数据平台治理的后门。结果截断必须对用户可见。如果只返回 Top 50 行,回答中就要标出这是 Top-K 结果,而非暗示已经覆盖全部 SKU;如果结果集被采样,结论应限制在样本范围。截断标记、总行数和排序依据都应进入 Planner 输入。很多错误解释并非 SQL 错,而是模型不知道自己看到的是截断数据。
34.6 从结果到业务解释¶
用户不关心 SQL 是否漂亮,用户关心结论是否可信。sql_executor 返回结果后,Planner 应先做结果摘要:Top-K 行、关键差异、聚合值、是否截断、样本量是否足够。随后把 metric_context、新鲜度和证据引用合并成可读回答。
{
"rows": [
{"sku": "SKU-A", "gmv_last_week": 1200000, "gmv_prior_week": 2100000, "delta_pct": -0.429},
{"sku": "SKU-B", "gmv_last_week": 980000, "gmv_prior_week": 1100000, "delta_pct": -0.109}
],
"row_count": 10,
"truncated": false,
"metric_context": [{"metric_id": "gmv_ops", "version": "2025Q1", "title": "运营 GMV"}],
"sql_hash": "b7e2a1",
"freshness": {"orders_fact": {"max_loaded_at": "2025-06-14T06:00:00Z"}}
}
用户可见回答可以是:
华东上周运营 GMV(
gmv_ops@2025Q1)较前周下降 12.3%。下滑贡献最高的 SKU 是 SKU-A,约占区域跌幅 32%;SKU-B 约占 11%。数据来自orders_fact v3,截至 2025-06-14 06:00 同步。
这段回答同时包含结论、口径、证据和新鲜度。它没有展示完整 SQL,但 Trace 中保存了 SQL hash、参数、Metric 版本和结果引用。用户继续问“和品类结构有没有关系”时,Planner 可以扩展 Question Frame,进入第35章的 Python 分析路径。如果结果为空,DataAgent 直接说“没有问题”会误导用户。它要区分真的无数据、过滤条件过严、口径默认过滤导致数据被排除、数据尚未同步和用户无权限。不同原因对应不同回答,也对应不同的下一步。业务解释还要避免过度归因。SQL 结果能告诉用户哪些 SKU 下滑,却无法自动证明原因。若用户问“是不是品类结构导致”,系统应进入第35章的分析路径;仅凭 Top SKU 排名给出因果判断,会把相关性写成结论。NL2SQL 负责取数和初步摘要,归因、预测和复杂统计需要更明确的分析步骤。
多轮追问时,SQL 结果应与 Question Frame 一起进入 Working Memory。用户问“那华北呢”,Planner 复用指标、时间和查询结构,只替换区域;用户问“按品类看”,Planner 增加维度并重新执行。这样可以保持上下文连续,又避免把上一轮 SQL 文本当成唯一依据。解释还要保留“不知道”的能力。如果 SQL 只返回了下滑 SKU,系统直接回答“原因是价格问题”就是越界;如果数据缺少促销字段,促销影响也无从判断。一个可信 DataAgent 会明确说“当前查询只能定位下滑 SKU,若要判断品类结构或价格因素,需要继续分析”。这种边界说明比强行给出结论更有价值。报告链路会复用本章结果。第36章生成图表和报告时,应使用本章产生的 result artifact、SQL hash 和 metric context。重新发起口径不明的 SQL,会让报告中的数字脱离原始查询。保留 artifact 后,每个结论都能回到当时执行过的查询。
34.7 从生成 SQL 到受控执行链路¶
sql_executor 是 Registry Tool。它的输入包括 SQL、租户、Metric context 和可选的 Linked Schema 摘要;输出包括结果摘要、artifact 引用、SQL hash、执行统计和结构化错误。Planner 只通过 Tool Call 使用它,不直接连数据库。
核心只读校验可以用 sqlglot 这类解析库实现。示意代码如下。
import sqlglot
from sqlglot import exp
def assert_readonly(sql: str) -> None:
tree = sqlglot.parse_one(sql, read="duckdb")
if not isinstance(tree, exp.Select):
raise ValueError("only read-only SELECT allowed")
真实实现要比这个示例更严格:处理 CTE、UNION、子查询、函数、导出语句和方言差异;校验表列是否属于 Linked Schema;在执行前跑 EXPLAIN;执行后截断结果并写 artifact。示例只说明 AST 校验的方向。目录上也要把生成和执行分开。agents/data_agent/ 可以负责 Question Frame、SQL 生成提示和解释模板;tools/sql_executor/ 只负责校验、执行和结果包装;infra/semantic_layer/ 负责 Metric 编译。混在一个模块里,短期实现快,长期很难替换数据库、改执行策略或接入新的语义层。
上线顺序可以从窄范围开始。先支持单 View、少量 Metric、只读 SELECT 和固定方言;再加入多方言、EXPLAIN 成本、artifact、错误自修复和评测集。一开始就承诺“任意自然语言生成任意 SQL”,会把产品预期推到执行器和语义层都承受不了的位置。DataAgent 的可信度来自可控范围,而非覆盖所有查询。第一批回归集可以很小,但要覆盖关键边界:正确查询、歧义指标、缺少时间、越权表、超时查询、空结果、截断结果、列名错误和方言错误。每个样本都要有期望行为,不一定都是成功出数。拒答、追问、失败和转人工同样是正确结果。运行日志也要服务产品改进。用户频繁触发列名错误,说明 Glossary 或 View 设计不足;频繁触发超时,说明默认时间范围或查询模板需要收紧;频繁出现空结果,可能是指标过滤或数据新鲜度提示不清。NL2SQL 是一个需要持续运营的工程系统,而非一次性模型能力。
上线前至少要准备三类回归。第一类是正常问数,例如华东周对比 Top SKU,确认结果包含 gmv_ops@2025Q1。第二类是安全拒绝,例如 DDL、DML、缺少租户过滤、访问禁表。第三类是自修复,例如列名错误、方言错误和可修正的聚合错误。只有成功路径和失败路径都可测,NL2SQL 才能进入生产。SQL 产物还要进入报告链路。每次成功执行后,sql_executor 应返回规范化 SQL hash、result artifact、行数、截断标记、新鲜度和 Metric context,而非只留下回答文本。第36章的图表和报告引用这些 artifact,避免重新让模型生成一条相似 SQL。这样用户从报告脚注点击回去时,看到的是当时执行过的查询,而非后来重新生成的解释。另一个容易忽略的边界是缓存失效。SQL 结果缓存必须包含 tenant、View、Metric 版本、时间范围、过滤条件、数据新鲜度和权限上下文。语义层变更、数据分区刷新、权限变化或用户切换角色后,缓存都可能失效。没有这些 key,缓存会把性能问题变成口径和权限问题。NL2SQL 的性能优化要以审计完整为前提。
34.8 NL2SQL 的失败回放与质量闭环¶
NL2SQL 上线后,失败往往分布在多个环节。用户问题可能含糊,语义层可能缺少术语,模型可能选错字段,SQL 可能能执行但口径错误,结果解释也可能把相关性写成因果。平台要把这些失败拆开记录,否则团队只会看到“问数失败”,不知道该改哪一层。一次可回放的 NL2SQL Run 至少要保存用户问题摘要、Linked Schema 版本、候选指标和字段、生成 SQL、执行前校验结果、执行资源用量、结果摘要和解释文本。敏感数据不必长期保存原文,但引用 ID、字段名、行数、聚合方式和错误码要保留。这样业务质疑结果时,平台能回到当时的语义版本和执行证据,而非重新跑一次已经变化的数据。自修复也要有边界。字段不存在、类型不匹配、时间窗口格式错误,可以允许模型根据错误信息修正一次;权限拒绝、资源超限、跨租户访问和写操作请求要直接停止。否则模型会把安全策略当成普通错误,反复寻找绕过路径。第50章的安全策略应优先于本章的自修复策略。评测集应覆盖真实业务语言,标准 SQL 题只能作为基础样本。DataAgent 的测试样本要包含口语化指标、时间表达、区域别名、权限差异、空结果、歧义问题和需要澄清的场景。每个失败样本都要能归因到语义层、生成模型、执行校验或解释层。只有这样,NL2SQL 才能从“生成 SQL”变成“受控问数链路”。
34.9 查询链路的灰度发布¶
NL2SQL 的发布要按查询链路管理。一个完整查询链路包括问题理解、语义层链接、SQL 生成、执行前校验、只读执行、结果解释和前端展示。任何一环变化,都可能改变最终答案。平台在灰度时应按链路发布,而非只替换 prompt 或模型。灰度样本要覆盖真实用户问题,标准 benchmark 只能作为其中一类输入。标准数据集能衡量 SQL 生成能力,但企业上线还要看指标别名、组织口径、权限差异、空结果、超时、澄清和解释质量。比如“看一下上周华东复购有没有异常”这种问题,至少涉及时间归一化、区域映射、指标定义、异常阈值和结果解释。只用标准 SQL 正确率无法覆盖这些风险。
灰度期间要保存新旧链路对照。平台可以让少量租户或影子流量同时跑旧链路和新链路,比较生成 SQL、执行结果、引用指标和解释文本。差异不一定都是错误,但每个高影响差异都要能归因。若新链路选择了不同指标版本,应该说明这是预期变更还是语义层回归;若新链路减少了澄清问题,应该检查是否牺牲了安全边界。发布门禁也要包含人工抽检。NL2SQL 的很多错误发生在业务解释层,而非语法层。系统可以自动检查 SQL 是否只读、是否命中权限、是否能执行、结果行数是否合理,但“这个解释是否符合业务口径”仍需要业务样本和人工判断。早期可以只抽检高频指标和高风险数据域,随着评测集增长再逐步自动化。
灰度还要区分模型变更和平台变更。换模型、改 Prompt、调整 Linker、更新语义层、修改执行器阈值,都会改变最终回答,但风险来源完全不同。发布记录应写清楚本次变更触达哪一层,以及哪些样本用于证明风险可控。否则线上出现差异时,团队很容易把所有问题都归到模型上,错过真正的根因。对于高价值数据域,灰度期间可以让新链路只生成建议,不直接影响用户可见答案。平台把新链路的 SQL、执行结果和解释文本保存下来,由数据团队和业务负责人抽查。等差异稳定、解释可信、资源消耗可控以后,再把新链路放给一部分真实用户。这样做会拉长发布周期,但能避免一次模型或语义层变更直接影响经营会议、财务复盘等正式场景。
34.10 查询失败后的交互恢复¶
NL2SQL 失败后,只返回一段技术错误无法帮助用户继续。不同失败类型对应不同恢复方式:问题歧义应进入澄清,权限不足应进入申请或降级,SQL 语法错误可以自修复,资源超限应提示缩小范围,空结果应解释过滤条件和数据新鲜度。把这些失败都交给模型自由解释,会让用户看到语气友好的错误,但无法继续完成任务。澄清问题要尽量结构化。用户问“上周销售怎么样”时,系统可以要求补充区域、渠道、指标口径或对比基准;每个澄清项都应来自语义层可识别的维度,而非模型临时发挥。用户选择后,新的条件应进入 Question Frame 和 Trace,后续 SQL 生成才能复现。若澄清只存在于自然语言对话里,Run 回放时很难还原最终查询条件。
空结果也需要区分原因。可能是业务确实没有数据,也可能是权限过滤后没有候选,也可能是时间窗口错误,还可能是数据延迟。平台应把执行结果、过滤条件、数据新鲜度和权限命中情况分开记录,再由报告层生成用户可读解释。否则模型可能把空结果解释成业务异常,误导后续判断。恢复策略还要考虑成本。一次失败后自动扩大时间范围、改写 SQL、换模型重试,看似提高成功率,实际可能放大仓库扫描成本。生产系统应限制自修复轮次和查询预算,并在超限时给出明确的下一步:缩小范围、申请权限、等待数据刷新或转人工分析。这样 NL2SQL 才能在可控成本内提供帮助。
交互恢复还要保护用户心智。系统如果连续追问多个技术条件,业务用户很快会放弃;如果系统直接给出技术错误,用户又无法判断下一步怎么办。比较好的做法是把恢复路径做成业务选择,例如“按运营 GMV 继续”“改看区域汇总”“等待数据刷新后提醒我”“转给数据负责人”。这些选项背后仍然是 Metric、View、权限和 Runtime 状态,但用户看到的是可以继续推进任务的动作。恢复后的状态也要写入 Trace。用户选择了新的指标口径、缩小了时间范围或接受了延迟数据,都会影响最终答案的责任归属。后续如果有人复盘报告,平台需要知道结果是在什么限制条件下生成的。否则恢复过程只存在于聊天记录里,报告和审计链路会丢失关键上下文。
34.11 失败回放的最小证据包¶
NL2SQL 的失败回放要覆盖最终 SQL 之外的链路证据。一次失败通常跨越语义层、生成器、校验器、执行器和解释层,缺少任一环节的证据,复盘都会变成经验判断。最小证据包应当包含用户原始问题、会话上下文、语义层版本、linked schema、候选指标与字段、生成 Prompt 或工具参数、模型输出、校验错误、执行计划、数据库返回、解释文本和用户可见结果。证据包要保证每个责任边界都有可以复查的输入和输出,不能用“收集越多越好”替代边界设计。
回放包要能回答三类问题。第一类是生成问题:模型是不是选错表、漏掉过滤条件、误解时间口径,或者把业务词映射到了错误指标。第二类是平台问题:语义层是否给出了足够上下文,权限系统是否裁剪了必要字段,SQL 校验器是否放过了危险查询。第三类是交互问题:用户问题是否缺少必要条件,系统是否提出过澄清,用户是否接受了降级答案。只有这些问题能被逐项定位,NL2SQL 的修复才不会落到“再调 Prompt”这一个出口。
失败样本进入评测集时也要保留失败类型。字段消歧失败、时间窗口错误、权限拒绝、结果为空、资源超限、解释不一致,对应不同责任边界。把它们放在同一个准确率里平均,会掩盖真实风险。第39章的评测如果只看答案是否正确,会低估执行链路风险;第38章的 Trace 如果只看模型调用,会漏掉语义层和数据库层的责任。NL2SQL 章节需要把这两个章节提前接起来,让读者明白:问数系统的质量来自从问题到证据包的全链路,而非单次生成质量。
34.12 查询链路的生产降级策略¶
生产环境里的 NL2SQL 要把失败当成常态路径处理。低风险失败可以要求用户补充条件,例如时间范围、业务域或指标口径;中风险失败可以返回候选查询解释,让用户确认后再执行;高风险失败必须停止执行,并把原因交给人工复核或数据负责人。分级依据不只看模型置信度,还要看权限、数据敏感度、预计扫描量、历史失败类型和用户操作意图。降级策略要写进 Runtime,而非散落在前端提示里。前端可以展示澄清问题和替代路径,但真正决定能否继续执行的是后端状态机。一次查询如果从 running 进入 waiting_human,审批通过后恢复执行,Trace 里必须能看到暂停点、审批人、审批依据和恢复后的 SQL。否则系统看似有人工确认,实际上只是一个界面按钮,无法承担审计责任。
当查询返回空结果或异常波动时,系统要先判断证据状态,再生成业务解释。空结果可能来自权限不足、过滤条件过窄、数据未刷新或真实业务为零;异常波动可能来自口径切换、数据延迟或事实变化。DataAgent 可以给出候选解释,但要把证据等级标出来,并提示需要的下一步验证。这样做会让回答显得更克制,却更接近企业场景的使用方式。问数系统除了给答案,也负责说明答案在什么条件下成立。
降级策略还要和前端展示配合。只读问数失败时,前端可以显示澄清面板;资源超限时,可以让用户选择更短时间范围或更粗粒度;权限不足时,可以展示申请入口或可用聚合层级;报告证据不足时,可以把草稿标记为“待复核”。这些体验应由后端状态和错误码驱动,而非由模型临时生成。前端负责把状态呈现清楚,后端负责保证状态可审计。有些场景还需要“部分成功”的表达。一次经营分析可能成功查到销售和库存,却因为客服数据延迟无法判断投诉影响。系统不必把整个 Run 判为失败,也不能给出完整归因。更好的做法是输出已完成证据、缺失证据和建议下一步,让用户知道哪些结论可以使用,哪些结论需要等待或人工补充。这种部分成功比简单失败更贴近真实业务。
34.13 SQL 生成能力的分层发布¶
NL2SQL 的查询能力适合分层开放。第一层可以只支持单指标、单时间窗口、少量维度过滤的只读查询;第二层再支持分组、排序、同比环比和简单归因;第三层才考虑多表 join、窗口函数、子查询和复杂分析。分层发布能让团队逐步验证语义层、校验器、资源控制和用户交互,不必在早期就承担所有 SQL 风险。每一层都要有明确退出条件。单指标查询稳定后,才能开放多维分组;资源保护稳定后,才能开放更大时间范围;失败回放稳定后,才能扩大到更多业务域。若某一层出现高频失败,平台应先收窄能力,而非继续扩大模型自由度。能力边界写清楚,业务团队也更容易理解系统当前能做什么。分层发布还帮助评测设计。不同层级使用不同样本和指标,避免简单问题掩盖复杂问题。第39章的评测可以按查询层级统计准确率、执行成功率、澄清率和人工介入率。这样 NL2SQL 的成熟度就能被持续观察,而非只在发布前做一次人工验收。
分层发布也有助于团队分工。语义层团队先保证核心指标和常用维度稳定,执行器团队先把只读、租户过滤和成本阈值做牢,产品团队先把澄清、拒答和证据展示做顺。等这些基础能力稳定后,再让模型处理更复杂的多表查询和诊断问题。若基础层没有通过验收,继续扩大生成能力只会让错误更难定位。对于业务方来说,分层发布还提供了清楚预期。第一阶段可以承诺“核心指标能问、能追溯、能拒答”,第二阶段再承诺“常见对比和分组能稳定完成”,第三阶段才承诺“复杂诊断可以进入分析链路”。这样比一句“自然语言查全库”更诚实,也更容易建立长期信任。分层发布还要保留收缩机制。某个数据域上线后,如果越权拦截、资源超限或人工退回明显增加,平台应能把它退回上一层能力,而非继续扩大覆盖。退回并不代表项目失败,它说明系统在用生产证据调整自动化程度。对企业来说,一个会收缩边界的 NL2SQL,比一个始终承诺全自动的系统更可靠。
每次层级提升都应伴随用户教育。第一层用户要理解口径脚注和拒答原因;第二层用户要理解同比、环比、分组和截断标记;第三层用户要理解复杂诊断需要更多证据,SQL 排名不能直接当成因果结论。产品界面可以把这些教育融入任务模板和结果说明,而非单独做培训课。用户知道系统能力边界后,错误使用会明显减少。NL2SQL 的成熟最终体现在运行纪律上。模型可以越来越强,但生产系统仍要坚持语义层约束、执行前校验、资源预算、错误分类、Trace 回放和灰度发布。少了这些纪律,模型能力提升会让系统更敢回答,却不一定让结果更可信。DataAgent 要把生成能力压进工程流程里,才能从演示问数走向企业问数。
工程团队还要处理“看似成功”的查询。SQL 执行成功、返回行数合理、回答语言顺畅,并不代表任务完成。系统还要检查是否使用了正确 Metric,是否继承了上一轮 Question Frame,是否显示了截断标记,是否把数据新鲜度带入回答。很多线上争议发生在这类成功路径里,因为平台没有把“业务正确”纳入执行后的校验。因此,NL2SQL 的验收样本要同时覆盖数据库层和业务层。数据库层看语法、方言、权限、资源和返回结果;业务层看口径、时间、维度、解释强度和可追溯性。同一条 SQL 可以在数据库层通过,在业务层失败。把这两层拆开记录,团队才能知道是执行器问题、语义层问题,还是回答模板问题。
前端展示也要配合查询链路。用户看到的结果区应包含核心数字、口径标签、数据时间、是否截断和必要的下一步动作。SQL 原文可以放进审计或展开区,不必直接展示给普通业务用户;但 SQL hash、artifact 和 EvidenceRef 必须存在。这样既不把用户拖进技术细节,又能保证报告、图表和后续追问都能回到同一次查询。当系统进入多轮问数时,NL2SQL 还要防止上下文漂移。用户追问“那华北呢”“按品类再看一下”“去年同期呢”,每一句都需要继承或修改 Question Frame。模型如果只根据聊天历史重新生成 SQL,很容易丢掉上一轮的 Metric 版本、过滤条件或数据域。更稳的做法是让 Planner 明确生成 Frame diff,再由语义层和执行器重新校验。这样多轮体验看起来像自然对话,底层仍然是结构化任务推进。
最后还要把 SQL 能力和报告能力分开验收。查询回答可以在秒级返回,报告生成可能需要更多数据、图表和人工确认。NL2SQL 只能证明某次取数可信,报告结论还要经过报告层的证据组织和复核。报告层引用 SQL artifact 时,应保留查询时间、数据快照、截断状态和指标版本,避免报告生成阶段重新解释一遍已经变化的数据。这样第34章的查询链路才能稳定支撑第36章的报告链路。这条边界对产品也有帮助。用户问数时可以接受短回答和可展开证据;用户生成经营报告时,则需要更完整的图表、限制说明和复核入口。把两类体验混在一起,会让简单问数变慢,也会让正式报告缺少审计材料。NL2SQL 负责把数据取准、取稳、取可回放,报告层再决定如何组织叙事。
34.14 查询链路的变更复核¶
NL2SQL 的变更复核要围绕查询链路展开,而不能只看 SQL 生成结果。一次看似很小的调整,例如更新语义层别名、增加默认时间窗口、收紧执行器成本阈值、替换模型路由,都会改变用户最终看到的数字、解释和恢复路径。复核时应先拆出变更影响的层级:问题理解是否变化,Linked Schema 是否变化,候选 Metric 是否变化,SQL 形态是否变化,执行计划是否变化,解释模板是否变化。每一层都要有样本对照,避免把所有差异都归因于模型输出。
变更复核的核心材料是新旧链路对比。平台应抽取高频问题、高风险指标、历史失败样本和权限边界样本,分别运行旧链路和新链路,比较 Question Frame、语义层版本、生成 SQL、执行计划、返回行数、截断标记、解释文本和 EvidenceRef。若新链路生成了不同 SQL,但 Metric、过滤条件、执行结果和解释强度都一致,差异可以接受;若 SQL 语法正确却切换了指标版本、丢失了权限过滤或改变了默认时间范围,就必须进入人工复核。复核记录要说明差异来源,而不是只写“通过”。
语义层漂移尤其需要单独处理。业务同义词、维度别名、指标默认口径和废弃状态会随业务变化更新。若这些变化没有绑定版本,历史问题回放时会得到不同候选指标,读者很难判断是模型变好、数据变了,还是口径被调整。更稳的做法是让每次 NL2SQL Run 记录语义层版本,并在变更发布时保留一段兼容窗口。旧报告和历史 Trace 使用旧版本解释,新问题可以逐步切到新版本;高风险指标则需要新旧版本并行对照,直到业务 owner 确认差异可接受。
执行器变更也要复核。成本阈值降低后,原本可执行的长时间窗口查询可能变成资源超限;只读校验规则收紧后,包含复杂 CTE 的安全查询可能被拒绝;结果截断策略变化后,报告层可能拿不到足够样本解释波动。这些变化不一定是错误,但必须影响用户可见行为。发布记录应把执行器配置、数据库方言、成本估算规则和错误码版本写清楚,并把被影响的问题样本加入回归集。否则线上用户看到的只是“以前能问,现在不能问”,平台却无法给出解释。
回滚策略要按数据域设计。一个模型路由问题可以按租户或 Agent 回退;一个语义层口径问题可能只影响某个指标域;一个执行器错误可能影响所有 NL2SQL 任务。平台不应只有全局回滚按钮,而要能按租户、数据域、Metric、View 或能力层级收缩。回滚后还要保留差异样本,进入第39章的评测集和第38章的 Trace 复盘。这样变更复核才会形成持续改进:每次发布会改变能力,也会把新的失败样本沉淀到质量体系中。
34.15 SQL Artifact 与用户解释的差异复盘¶
NL2SQL 链路上线后,SQL artifact 和用户解释之间可能出现差异。SQL 执行结果是正确的,但回答把趋势解释错了;SQL 过滤条件缺少一个维度,回答却给出确定结论;SQL 返回空结果,系统把它解释成业务为零;SQL 使用了近似聚合,回答没有说明误差范围。这些问题不一定来自 SQL 生成失败,而是发生在结果解释和证据表达阶段。
差异复盘要同时保留 SQL、执行结果和解释文本。平台应记录用户问题、Linked Schema、生成 SQL、校验结果、执行引擎、结果样本、图表配置、解释文本、EvidenceRef 和用户反馈。若用户质疑结论,团队可以判断问题发生在 SQL、执行、后处理、图表还是自然语言解释。只保存 SQL 无法解释为什么用户看到的报告错了;只保存回答文本也无法定位底层查询是否正确。
复盘时要按差异类型修复。SQL 正确但解释错误,优先改报告模板、解释规则和 LLM-as-Judge 样本;SQL 缺字段但解释确定,优先改执行前校验和澄清策略;空结果解释错误,优先改空值、缺失和权限裁剪的提示;近似聚合未说明误差,优先改指标元数据和脚注。不同差异类型对应不同修复入口,不能全部归为模型回答质量问题。
早期可以为 DataAgent 查询结果增加解释复核样本。样本不追求覆盖所有问法,而是覆盖最容易引发业务争议的结果形态:空结果、异常值、同比环比、TopN、权限裁剪、近似聚合和多指标对比。这样第34章的 NL2SQL 不会停在 SQL 生成和执行,还能覆盖用户真正读到的业务解释。
34.16 SQL 失败回放与解释修正¶
NL2SQL 失败不能只保存错误 SQL。许多失败来自问题理解、语义层映射、权限过滤、时间窗口、指标口径或结果解释。若平台只记录生成 SQL 和数据库报错,团队很难判断模型需要修、语义层需要修,还是用户问题需要澄清。失败回放要保留完整链路:原始问题、Question Frame、语义层候选、生成 SQL、校验结果、执行结果、解释文本和用户反馈。
回放样本要区分失败类型。语法错误进入 SQL 生成样本;字段错误进入语义层映射样本;权限错误进入策略样本;结果为空进入澄清或数据可用性样本;解释错误进入报告层样本。不同失败对应不同 owner。把所有失败都交给模型微调,会让问题定位变慢,也会把数据和权限问题掩盖成模型问题。
早期可以在每次 NL2SQL 失败后生成复盘包。复盘包包含失败标签、证据引用、建议修复点和是否进入回归集。人工修正 SQL 后,也要保存修正理由,而不是只保存正确 SQL。这样 DataAgent 能从失败中积累可复用样本,SQL 生成、语义层和解释层也能分别改进。
34.17 查询执行链路的用户承诺¶
NL2SQL进入生产后,新增能力不能只看功能是否可用,还要看运行证据能否被不同角色复用。平台需要把查询范围、资源上限、只读约束、结果解释、失败原因和修订记录记录成稳定字段,并和发布单、Trace、评测样本以及事故记录关联起来。这样一次线上问题发生后,团队可以沿着同一组事实判断影响范围、责任归属和修复顺序,而不是在模型日志、业务日志和人工说明之间来回拼接。
这类证据还要服务相邻章节的能力。它和第33章语义层、第35章 Python 分析和第38章 Trace相连:上游能力提供输入假设,下游能力使用执行结果,治理能力负责保存证据和复审结论。若这些材料没有统一编号和版本,章节里讨论的工程能力在生产中会被拆散。业务 owner 只能看到用户投诉,平台 owner 只能看到系统错误,安全或合规团队只能看到事后说明,最后很难判断问题到底来自数据、模型、工具、流程还是组织责任。
生产环境中常见的风险包括用户不知道查询限制、系统把近似结果写成确定结论、失败后只给数据库错误。这些问题在演示阶段不明显,因为演示通常只覆盖成功路径;上线后,用户会带来边界问题、重复请求、权限变化和长时间运行状态。平台团队应把失败样本纳入发布节奏,记录哪些样本需要阻断发布,哪些样本可以通过降级处理,哪些样本需要业务 owner 接受剩余风险。
查询链路应向用户说明可回答范围,并把失败恢复设计成产品体验的一部分。这份记录不需要复杂,但要包含时间、版本、owner、样本、处置动作和下次复查条件。没有这些字段,复盘会停留在口头经验;有了这些字段,平台才能把一次问题转成后续发布、评测和培训材料。
早期平台可以从少量高风险场景开始。先选择调用量高、业务影响大或涉及敏感数据的路径,要求每次变更都留下证据包,再逐步推广到普通场景。这样章节里的能力不会停留在概念层,而会成为可运行、可解释、可退回的工程系统。
本章小结¶
DataAgent 中的 NL2SQL 是语义层、Planner、Registry 和 sql_executor 的协作环,不是单次 Prompt。生成 SQL 前应先使用 Linked Schema 和 View 剪枝,避免把全库 DDL 放进模型上下文,也减少误连表和错口径的概率。SQL 执行前必须经过语法、只读、Schema、成本和 Policy 校验。自修复只适合语法、字段名、聚合口径等可修复错误;越权和敏感访问应直接拒绝。用户真正需要的是带口径、新鲜度和证据的业务解释,SQL 只是中间产物。
参考文献¶
Liu, X., et al. (2025). A survey of Text-to-SQL in the era of LLMs. IEEE TKDE, 37(10), 5735-5754. https://doi.org/10.1109/TKDE.2025.3592032
Tang, Z., et al. (2025). LLM/Agent-as-Data-Analyst: A survey. arXiv:2509.23988. https://arxiv.org/abs/2509.23988
Lei, F., et al. (2024). Spider 2.0: Evaluating language models on real-world enterprise text-to-SQL workflows. ICLR 2025. arXiv:2411.07763. https://arxiv.org/abs/2411.07763
Gao, D., et al. (2023). Text-to-SQL empowered by large language models: A benchmark evaluation. VLDB. arXiv:2305.03111.
Pourreza, M., & Rafiei, D. (2023). DIN-SQL: Decomposed in-context learning of text-to-SQL with self-correction. NeurIPS. arXiv:2304.11015. https://arxiv.org/abs/2304.11015
Talaei, S., et al. (2024). CHESS: Contextual harnessing for efficient SQL synthesis. arXiv:2405.16755. https://arxiv.org/abs/2405.16755
Li, H., et al. (2024). CodeS: Towards building open-source language models for text-to-SQL. SIGMOD 2024. arXiv:2402.16347. https://arxiv.org/abs/2402.16347
