Skip to content

Text-to-SQL / ChatBI 生产系统设计:语义层、安全边界与可信回答

“让用户用自然语言查询数据库”是大模型应用面试里极其常见、也特别容易答成 Demo 的题目。Demo 只要把表结构塞进 Prompt,要求模型返回 SQL,再执行即可;企业级 ChatBI 的难点则是:同一个“销售额”到底含不含退款、一个用户能看哪些行和列、生成的 SQL 会不会扫全表、结果能否解释、错误时怎样修复,以及怎样证明回答没有把口径讲错。

本页只聚焦 Text-to-SQL / ChatBI 的生产控制面。通用 JSON 约束见 结构化输出,数据源增量与权限变化见 企业 Connector 增量同步与撤权,模型调用的流控与成本治理见 LLM 准入与过载控制

一、先反驳错误前提:这不是“翻译 SQL”

用户问“华东区本季度销售额环比怎么样”,系统至少需要解决:

  1. 实体消歧:华东区是销售组织、收货地址还是客户归属地?
  2. 指标口径:销售额是含税下单金额、支付金额、发货金额还是扣除退款后的净额?
  3. 时间语义:“本季度”采用自然季度还是财务季度,时区和数据截止点是什么?
  4. 权限边界:提问者可否看到所有地区、所有客户、利润字段或明细行?
  5. 查询代价:查询会否扫描数十亿行、跨多个数据仓库,或造成资源争用?
  6. 结果表达:环比结论的数值、分母、异常和引用的数据集版本能否被复核?

因此,ChatBI 的核心不是把自然语言映射到任意 SQL,而是将问题映射为一个受治理的分析意图,再由语义层、权限层和查询编译器生成受限查询。LLM 在这条链路中是意图理解与候选计划器,而不是数据库管理员。

二、先建设语义层:让业务词汇有唯一含义

直接把原始 schema 暴露给模型,通常会得到看似合理却口径错误的 SQL。建议为每个可用指标、维度和实体建立语义目录:

text
semantic_metric
  -> name: net_revenue
  -> definition: paid_amount - refunded_amount, excluding cancelled orders
  -> grain: order
  -> allowed_dimensions: region, product_category, paid_date
  -> source_model: mart_order_revenue_v4
  -> owner / freshness SLA / certification status

semantic_dimension
  -> name: sales_region
  -> aliases: 华东, 东区
  -> business meaning / hierarchy / row-level policy

这里的 definition 最好对应可审查的指标代码、受认证的视图或 dbt/BI 模型,而不是一段仅供阅读的自然语言说明。模型选择 net_revenue 后,由确定性编译器展开其计算逻辑。这样“销售额”口径变更只更新语义层,避免每个 Prompt、报表和 Agent 各自解释一次。

指标治理要点

问题生产做法
同名不同义指标 ID 与显示名称分离;名称冲突时要求澄清或给出候选口径
多表连接在语义层声明允许 join 路径、基数和去重规则,不让模型自由猜 join key
数据新鲜度显示数据截止时间、延迟和是否为实时/日批;过期时降低结论强度
认证与责任标识 certified 指标、owner、测试状态和弃用日期
单位与币种将单位、汇率日期、精度和四舍五入规则作为类型信息,而非文本后处理

面试亮点:我会先问“销售额的业务定义是否已经固化在语义层”,而不是立刻讨论模型选型。没有指标治理,Text-to-SQL 的准确率没有可验证的上限。

三、数据目录检索:只给模型任务相关的 schema

大型数仓往往有数千张表,把完整 DDL 放进上下文既昂贵又会增加歧义。应建立可检索的数据目录,包含表/列说明、业务别名、指标定义、血缘、示例问题、认证状态、敏感等级和最近使用信号。

在线检索通常分为三步:

text
natural language question
  -> intent extraction (metric / dimensions / time / comparison / ambiguity)
  -> retrieve semantic candidates and approved query patterns
  -> select a compact schema context with join graph and policies

检索不应只用向量相似度。用户提到“退款”时,关键词、业务别名和指标依赖很关键;用户角色、租户、数据域和表的新鲜度也必须作为过滤条件。高风险或低置信度场景返回“请确认你指的是净销售额还是下单金额”,比模型擅自选一个更可信。

四、从自然语言到受限查询计划

推荐先输出一个中间表示(IR/DSL),再编译成特定引擎 SQL,而不是直接生成可执行 SQL:

json
{
  "metric": "net_revenue",
  "dimensions": ["sales_region"],
  "filters": [{"field": "paid_date", "op": "between", "value": ["2026-04-01", "2026-06-30"]}],
  "comparison": {"type": "previous_period"},
  "limit": 100,
  "explanation_required": true
}

IR 的 schema 只允许已登记的指标、维度、比较方式和过滤运算符。服务端校验后再由编译器生成 SQL,统一加上行级权限、时间范围、资源组、LIMIT 和安全的参数绑定。这样能避免模型写入 DDL/DML、调用危险函数、拼接注入字符串或绕开租户条件。

对于确实需要自由 SQL 的高级分析师模式,也必须进入独立的 sandbox:只读服务账号、表级 allowlist、短超时、扫描字节上限、审计和显式风险提示;不要把“高级模式”变成无约束的生产数据库入口。

五、SQL 安全不止禁止 DELETE

SQL AST 解析器应在真正执行前做静态检查。常见规则如下:

  • 只允许 SELECT/受控的 WITH,拒绝多语句、DML、DDL、存储过程和外部网络函数;
  • 表、列、函数、join 路径必须来自语义目录和 allowlist;
  • 强制注入租户、组织、地域或数据域的行级过滤,且禁止用户文本覆盖;
  • 拒绝笛卡尔积、无界时间范围、未聚合的大明细扫描和高基数无限排序;
  • 用查询引擎的 EXPLAIN/dry-run 估计扫描量、分区命中和成本;
  • 超过预算时改写为抽样、汇总、异步任务,或要求用户缩小范围;
  • 为每次查询设置 statement timeout、并发配额、结果行数和导出字节上限。

“模型不会生成删除语句”不是安全策略。实际风险更多来自一个合法的 SELECT 扫描全库、把敏感列加入结果、错误 join 放大金额,或在查询日志中泄露用户提供的身份信息。

六、权限:先过滤数据域,再决定回答能否展示

权限至少有三层:

text
Question access: 用户能否调用这个数据域/指标?
Query access: 编译后的 SQL 是否带上正确的行、列、脱敏策略?
Answer access: 结果摘要、图表、明细下载和引用能否分别展示?

例如区域负责人可以看本区域汇总,不能看其他区域明细;财务可以看毛利,销售只能看收入;一个允许回答“本季度增长 8%”的用户,也未必可以下载客户级明细。权限计算必须在数据库或可信查询网关中强制执行,不能只让模型“不要提到利润”。

如果检索到无权限的表描述、历史样例或缓存结果,同样可能泄露结构与数据。目录检索、Prompt 组装、语义缓存、日志与评测集都要继承权限标签。

七、纠错循环:让系统修复可诊断错误,而不是盲目重试

查询失败后,不能把数据库原始错误全文回灌给模型。错误消息可能泄露 schema,也可能诱导模型反复改写。应先映射为受控错误类型:

错误类型例子系统动作
意图歧义“活跃客户”定义不唯一追问用户,展示定义差异
语义不可达没有认证指标或允许的 join拒答并指向数据 owner
编译失败不支持的聚合组合返回安全的错误码,可有限次修复 IR
成本超标预估扫描 20TB建议时间范围/聚合,或转异步审批
空结果筛选条件无数据展示已解释的过滤条件,建议放宽
执行异常数据仓库暂不可用重试或降级,不重新解释业务口径

纠错循环应该有最大轮数和 token/时间预算。每一轮记录输入 IR、校验错误和改写原因;若连续失败,交由人工数据分析师或返回可操作的澄清问题。系统不能把“不知道”伪装成一个随意修改后成功执行的查询。

八、结果对账与可信表达

SQL 执行成功并不代表答案可信。结果层应做:

  1. 类型和范围检查:金额、百分比、分母、时间边界、空值和异常值是否符合指标规则。
  2. 对账查询:对高价值指标使用独立聚合、历史快照或认证 BI 看板抽样比对。
  3. 结论约束:模型只能依据返回行和计算字段描述趋势;无数据时不能生成归因。
  4. 可解释结果卡:显示指标定义、过滤条件、时间范围、数据截至时间、查询 ID 与数据集版本。
  5. 图表契约:图表由结构化数据生成,模型给标题和说明,不允许让模型凭文本画数值。

对于“为什么下降”这类因果性问题,要明确区分描述统计、相关性和已验证因果。系统可建议按渠道、地区、品类分解,但不应把相关变化说成确定原因,除非接入了相应的实验或因果分析证据。

九、架构与状态机

text
Client
  -> Auth / Policy Gateway
  -> ChatBI Orchestrator
       -> Semantic catalog & schema retrieval
       -> LLM intent-to-IR service
       -> IR validator / policy injector
       -> SQL compiler -> query gateway -> warehouse
       -> result validator / reconciliation
       -> answer renderer / chart service
  -> audit, trace, evaluation, feedback stores

一次请求可进入以下状态:

text
received -> clarifying -> planning -> validating -> estimating
  -> running -> reconciling -> answering -> completed
             -> blocked / denied / over_budget / failed / cancelled

每一步持久化 request_id、用户与数据域、语义层 revision、候选 IR、策略决定、编译 SQL 的哈希、查询 ID、成本、结果快照和最终解释。对长查询使用异步任务与可取消状态,流式返回进度时不得提前暴露未经权限过滤的中间数据。

十、评测体系:SQL 执行正确率不够

离线 golden set 应覆盖业务口径、同义表达、时间语义、权限边界、空数据、跨表 join、故意模糊问题和攻击输入。建议分层指标:

指标说明
intent / metric accuracy是否选对指标、维度、过滤和比较周期
execution accuracySQL 或 IR 运行结果是否与标准结果等价
semantic correctness结果虽能运行,业务口径是否正确
policy violation rate是否发生越权表/列/行访问或导出
cost compliance扫描量、时延、重试次数是否在预算内
answer faithfulness文字结论是否被结果集与定义支持
clarification quality对歧义问题是否正确追问而非猜测

注意 execution accuracy 的陷阱:两条 SQL 可能文本不同但结果等价,也可能在测试小数据上恰好结果相同。评测应比较语义 IR、关键中间结果和多组对抗数据。上线后将用户采纳、手动改写、空结果、成本超标、纠错失败和人工标注沉淀为 bad case;指标口径变更会使旧样本失效,因此评测集也必须版本化。

十一、系统设计答题框架

面试官让你“设计一个企业 ChatBI”时,建议按以下顺序组织:

  1. 澄清约束:面向业务员工还是分析师?是否允许明细下载?数据规模、刷新 SLA、合规地域和峰值 QPS 是什么?
  2. 定义成功指标:不只看 SQL 成功率,还看口径正确率、越权率、P95 延迟、扫描成本和人工澄清率。
  3. 语义层先行:由数据团队维护认证指标、维度、join 图、别名和 owner;模型只从许可目录选择。
  4. IR + 编译执行:LLM 输出受约束 DSL,服务端验证并注入策略,编译器产生参数化 SQL,网关 dry-run 后执行。
  5. 结果与解释分离:确定性服务负责数据和图表,LLM 只基于受控结果写摘要,并展示口径和数据截至时间。
  6. 可靠性与安全:预算、超时、限流、异步、幂等、RLS/CLS、审计、脱敏和外发控制。
  7. 闭环评测:黄金集、影子流量、用户反馈、失败分类和语义目录变更回归。

这样回答可以清晰体现:你没有把安全寄托在 Prompt,没有把业务口径寄托在模型记忆,也没有把“SQL 能跑”当成业务正确。

十二、高频追问与参考回答

Q1:为什么要用 DSL,直接让模型写 SQL 不更灵活吗?

DSL 用受控表达能力换取可校验性:可枚举指标和维度、统一注入权限、阻止危险语法、稳定做成本估算,并能跨 Trino、Snowflake、ClickHouse 等引擎编译。高级分析场景可提供 sandbox SQL,但不能让默认聊天入口拥有无限表达能力。

Q2:Schema RAG 和普通 RAG 有什么不同?

检索对象不是段落,而是具有结构关系与权限标签的表、列、指标、join 路径、样例与血缘。召回目标不是“最相似文本”,而是构成一个能回答当前问题、且合法可执行的最小 schema 子图。后续必须经语义和 AST 校验,不能把检索结果直接当真。

Q3:如何防止用户用自然语言绕过行级权限?

权限不由模型决定。身份与数据域由服务端传入,编译器或查询网关强制添加不可移除的谓词,数据库再用 RLS/视图做最后防线。Prompt、目录检索、缓存命中和最终答案都要使用相同的权限上下文。

Q4:SQL 成功但结果明显不合理时怎么办?

先检查指标粒度和 join 基数,典型问题是多对多 join 导致金额重复。对认证指标优先查询预聚合模型;对关键结果运行独立对账或阈值检测;发现异常时返回“结果需要复核”而不是生成自信结论,并将样本进入评测集。

Q5:怎样控制一次“帮我分析过去三年所有订单”的成本?

在执行前 dry-run 估算扫描量和队列时间,按用户/租户/任务设预算。超过阈值时建议按月聚合、限定地区、使用物化视图,或转为异步任务并等待审批;同时设置引擎级字节上限和超时。不能只依赖模型主动写 LIMIT,因为 LIMIT 未必减少底层扫描。

十三、60 秒项目讲法

“我将 ChatBI 设计为语义驱动的受限查询系统,而不是自然语言到任意 SQL 的翻译器。数据团队先维护认证指标、维度、join 路径和业务别名;用户问题经权限过滤后,模型只输出受 schema 约束的查询 DSL。服务端校验 DSL、强制注入行列权限与时间/成本策略,再由编译器生成参数化 SQL,并在 dry-run 后执行。结果层会校验粒度、金额范围和数据新鲜度,对关键指标做对账;模型只基于受控结果生成解释,同时展示指标口径、过滤条件和查询版本。我们用口径准确率、越权率、扫描成本、澄清质量和结论忠实度评测,而不仅仅看 SQL 能否执行。”

这段项目讲法覆盖了企业大模型应用岗最常见的连环追问:为什么需要 RAG、如何防越权、如何防幻觉、如何控成本、如何评估,以及当结果不可信时怎样降级。

基于 MIT 许可发布