当一个自然语言问题飞向数据库,中间到底应该发生什么?本文拆解一套生产级 NL2SQL 架构的 20 条硬规则、6 阶段状态机与三层指纹同步机制 ,并给出从接入到自我修复的完整使用路径。
git地址放在了最后。
一、为什么 99% 的 NL2SQL Demo 都不能上生产
打开任何一个 NL2SQL 的技术 Demo,你会看到这样的流程:用户输入「查询上个月销售额最高的 10 个产品」,AI 返回一段 SQL,执行后给出结果。看起来很美好。
但当你把它接上真实的业务数据库,问题立刻浮出水面:
| 用户说的 | AI 猜的 | 数据库里实际可能是 |
|---|---|---|
| 「张三的日报」 | name = '张三' |
nickname = '小张',staff_id = 'ZS001' |
| 「华东区」 | region = '华东' |
region_code = 'HD',area = '华东大区' |
| 「已通过」 | status = 'approved' |
status = 2,audit_status = 'pass' |
| 「昨天的数据」 | date = '2026-07-26' |
report_date = '2026-07-26',biz_date = '20260726' |
每一个「猜」都是一颗定时炸弹。状态枚举值理解错误,查询结果直接全错;时间字段用错,把提交时间当日报日期,数据完全对不上;JOIN 路径不对,数字翻倍或减半。更致命的是--用户看到一个数字「昨天 105 人提交日报」,如果不知道这个数字的置信度,可能会基于错误数据做出业务决策。
Demo 追求的是「能跑通」,生产追求的是「可信赖」。这两者之间的鸿沟,不是换一个更强的 LLM 就能填平的--它需要一套完整的工程架构来约束 AI 的行为,让每一步都有证据、有校验、有兜底。本文拆解的 nl2sql-analyst 架构,核心理念浓缩成一句话:
先建口径,再问数。 没有用户确认过的业务语义字典,不直接 NL2SQL;每条 SQL 执行前必须通过 25 条机械安全检查 + 独立语义审查;每次结果必须附带 5 维度置信度评级;低置信度问题自动进入债务闭环,通知维护人员修复,修复后验证置信度回升。
二、全景速览:从一个数据库接入到自我修复的完整旅程
在看架构细节之前,先用一个完整的使用路径把全貌看清。这套系统不是「问一句答一句」的单次交互,而是一个从接入、建字典、日常问数、问题发现到自动修复的生命周期。以下是五个典型场景,串起了系统的全部能力。
场景一:接入新库--先打分,再决定怎么建字典
用户:帮我连这个库问数,JDBC 是 jdbc:mysql://host:3306/mydb,用户名 readonly
AI 执行:
1. 识别数据源类型和方言(MySQL)
2. [可选] 询问是否配置通知渠道(飞书 webhook / 企微机器人)
3. 读取 information_schema,全库画像
4. 执行语义就绪度评估(6 维度评分)
5. 输出评估报告 + 等级(S/A/B/C/D)
6. 根据等级走双路径:
- S/A 级:AI 本地建字典草稿 -> 输出确认问题 -> 用户确认
- B/C/D 级:输出待补充备注清单 -> 维护人员改库 -> 重新评估
7. 确认完成后写入 confirmed_semantics + 绑定指纹
8. 进入稳定问数
关键点:不是连上库就能问。先评估元数据质量,注释好的库 AI 自动建字典让用户做判断题,注释差的库直接输出清单让维护人员回去补 COMMENT。这比 AI 瞎猜然后给出错误结果,成本低得多。
场景二:日常问数--12 步流水线,每步有校验
#mermaid-svg-KBsT84uL24ZlEnaE{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-KBsT84uL24ZlEnaE .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-KBsT84uL24ZlEnaE .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-KBsT84uL24ZlEnaE .error-icon{fill:#552222;}#mermaid-svg-KBsT84uL24ZlEnaE .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-KBsT84uL24ZlEnaE .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-KBsT84uL24ZlEnaE .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-KBsT84uL24ZlEnaE .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-KBsT84uL24ZlEnaE .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-KBsT84uL24ZlEnaE .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-KBsT84uL24ZlEnaE .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-KBsT84uL24ZlEnaE .marker{fill:#333333;stroke:#333333;}#mermaid-svg-KBsT84uL24ZlEnaE .marker.cross{stroke:#333333;}#mermaid-svg-KBsT84uL24ZlEnaE svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-KBsT84uL24ZlEnaE p{margin:0;}#mermaid-svg-KBsT84uL24ZlEnaE .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-KBsT84uL24ZlEnaE .cluster-label text{fill:#333;}#mermaid-svg-KBsT84uL24ZlEnaE .cluster-label span{color:#333;}#mermaid-svg-KBsT84uL24ZlEnaE .cluster-label span p{background-color:transparent;}#mermaid-svg-KBsT84uL24ZlEnaE .label text,#mermaid-svg-KBsT84uL24ZlEnaE span{fill:#333;color:#333;}#mermaid-svg-KBsT84uL24ZlEnaE .node rect,#mermaid-svg-KBsT84uL24ZlEnaE .node circle,#mermaid-svg-KBsT84uL24ZlEnaE .node ellipse,#mermaid-svg-KBsT84uL24ZlEnaE .node polygon,#mermaid-svg-KBsT84uL24ZlEnaE .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-KBsT84uL24ZlEnaE .rough-node .label text,#mermaid-svg-KBsT84uL24ZlEnaE .node .label text,#mermaid-svg-KBsT84uL24ZlEnaE .image-shape .label,#mermaid-svg-KBsT84uL24ZlEnaE .icon-shape .label{text-anchor:middle;}#mermaid-svg-KBsT84uL24ZlEnaE .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-KBsT84uL24ZlEnaE .rough-node .label,#mermaid-svg-KBsT84uL24ZlEnaE .node .label,#mermaid-svg-KBsT84uL24ZlEnaE .image-shape .label,#mermaid-svg-KBsT84uL24ZlEnaE .icon-shape .label{text-align:center;}#mermaid-svg-KBsT84uL24ZlEnaE .node.clickable{cursor:pointer;}#mermaid-svg-KBsT84uL24ZlEnaE .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-KBsT84uL24ZlEnaE .arrowheadPath{fill:#333333;}#mermaid-svg-KBsT84uL24ZlEnaE .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-KBsT84uL24ZlEnaE .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-KBsT84uL24ZlEnaE .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-KBsT84uL24ZlEnaE .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-KBsT84uL24ZlEnaE .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-KBsT84uL24ZlEnaE .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-KBsT84uL24ZlEnaE .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-KBsT84uL24ZlEnaE .cluster text{fill:#333;}#mermaid-svg-KBsT84uL24ZlEnaE .cluster span{color:#333;}#mermaid-svg-KBsT84uL24ZlEnaE div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-KBsT84uL24ZlEnaE .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-KBsT84uL24ZlEnaE rect.text{fill:none;stroke-width:0;}#mermaid-svg-KBsT84uL24ZlEnaE .icon-shape,#mermaid-svg-KBsT84uL24ZlEnaE .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-KBsT84uL24ZlEnaE .icon-shape p,#mermaid-svg-KBsT84uL24ZlEnaE .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-KBsT84uL24ZlEnaE .icon-shape .label rect,#mermaid-svg-KBsT84uL24ZlEnaE .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-KBsT84uL24ZlEnaE .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-KBsT84uL24ZlEnaE .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-KBsT84uL24ZlEnaE :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 读取确认口径
按需读取结构证据
候选值发现
锁定实际值
SQL 证据闭环
SQL 安全检查
25 条规则
语义二次审查
6 维度
只读执行
Sanity check
置信度评级
5 维度
答案+溯源
写入文件
输出结果
+置信度
债务后处理
用户看到的不是一段 SQL,而是用业务语言写的查询逻辑说明:「从日报主表中筛选昨天日期的记录,按工号去重统计」。技术细节(具体 SQL、执行结果行)留在溯源文件里,业务人员看得懂,技术人员查得到。
场景三:低置信度触发--系统主动发现问题
用户:库存周转率是多少?
AI 执行:
1. 定位表(fin_inventory)-> 校验指纹 -> 查询
2. 计算置信度 -> 🟠 58/100(C 级)
3. 扣分原因:fin_invoice.invoice_type 枚举值未确认(C1 -18)
4. 输出结果 + 置信度评估(标注低可信)
5. [后处理] 写入置信度债务表
6. 影响次数 ≥ 2 -> 触发通知
7. 已配置飞书 webhook -> 推送通知:
"表 fin_invoice 的字段 invoice_type 枚举值未确认,
建议执行 ALTER TABLE ... COMMENT '发票类型:1=增值税,2=普通...'"
8. 状态变 notified
系统不会把低置信度结果当成高置信度结果糊弄过去。它明确标注「这个结果不太可信」,并且把问题记入债务表,累计到阈值后主动通知维护人员去补备注。
场景四:修复后自动验证--闭环完成
#mermaid-svg-jV64zYSkVtC7u4bO{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-jV64zYSkVtC7u4bO .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-jV64zYSkVtC7u4bO .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-jV64zYSkVtC7u4bO .error-icon{fill:#552222;}#mermaid-svg-jV64zYSkVtC7u4bO .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-jV64zYSkVtC7u4bO .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-jV64zYSkVtC7u4bO .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-jV64zYSkVtC7u4bO .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-jV64zYSkVtC7u4bO .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-jV64zYSkVtC7u4bO .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-jV64zYSkVtC7u4bO .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-jV64zYSkVtC7u4bO .marker{fill:#333333;stroke:#333333;}#mermaid-svg-jV64zYSkVtC7u4bO .marker.cross{stroke:#333333;}#mermaid-svg-jV64zYSkVtC7u4bO svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-jV64zYSkVtC7u4bO p{margin:0;}#mermaid-svg-jV64zYSkVtC7u4bO .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-jV64zYSkVtC7u4bO .cluster-label text{fill:#333;}#mermaid-svg-jV64zYSkVtC7u4bO .cluster-label span{color:#333;}#mermaid-svg-jV64zYSkVtC7u4bO .cluster-label span p{background-color:transparent;}#mermaid-svg-jV64zYSkVtC7u4bO .label text,#mermaid-svg-jV64zYSkVtC7u4bO span{fill:#333;color:#333;}#mermaid-svg-jV64zYSkVtC7u4bO .node rect,#mermaid-svg-jV64zYSkVtC7u4bO .node circle,#mermaid-svg-jV64zYSkVtC7u4bO .node ellipse,#mermaid-svg-jV64zYSkVtC7u4bO .node polygon,#mermaid-svg-jV64zYSkVtC7u4bO .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-jV64zYSkVtC7u4bO .rough-node .label text,#mermaid-svg-jV64zYSkVtC7u4bO .node .label text,#mermaid-svg-jV64zYSkVtC7u4bO .image-shape .label,#mermaid-svg-jV64zYSkVtC7u4bO .icon-shape .label{text-anchor:middle;}#mermaid-svg-jV64zYSkVtC7u4bO .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-jV64zYSkVtC7u4bO .rough-node .label,#mermaid-svg-jV64zYSkVtC7u4bO .node .label,#mermaid-svg-jV64zYSkVtC7u4bO .image-shape .label,#mermaid-svg-jV64zYSkVtC7u4bO .icon-shape .label{text-align:center;}#mermaid-svg-jV64zYSkVtC7u4bO .node.clickable{cursor:pointer;}#mermaid-svg-jV64zYSkVtC7u4bO .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-jV64zYSkVtC7u4bO .arrowheadPath{fill:#333333;}#mermaid-svg-jV64zYSkVtC7u4bO .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-jV64zYSkVtC7u4bO .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-jV64zYSkVtC7u4bO .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-jV64zYSkVtC7u4bO .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-jV64zYSkVtC7u4bO .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-jV64zYSkVtC7u4bO .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-jV64zYSkVtC7u4bO .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-jV64zYSkVtC7u4bO .cluster text{fill:#333;}#mermaid-svg-jV64zYSkVtC7u4bO .cluster span{color:#333;}#mermaid-svg-jV64zYSkVtC7u4bO div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-jV64zYSkVtC7u4bO .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-jV64zYSkVtC7u4bO rect.text{fill:none;stroke-width:0;}#mermaid-svg-jV64zYSkVtC7u4bO .icon-shape,#mermaid-svg-jV64zYSkVtC7u4bO .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-jV64zYSkVtC7u4bO .icon-shape p,#mermaid-svg-jV64zYSkVtC7u4bO .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-jV64zYSkVtC7u4bO .icon-shape .label rect,#mermaid-svg-jV64zYSkVtC7u4bO .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-jV64zYSkVtC7u4bO .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-jV64zYSkVtC7u4bO .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-jV64zYSkVtC7u4bO :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 维护人员执行
ALTER TABLE ... COMMENT '发票类型:1=增值税专票,2=普通发票'
用户下次查询涉及 fin_invoice
AI 指纹校验
结构指纹变了
读取最新元数据
发现注释变好了
自动更新字典
债务状态变 resolved
计算置信度
🟡 82/100(A 级,回升)
债务状态变 verified ✅
闭环完成
维护人员不需要告诉 AI「我改好了」。下次有人查到这张表时,指纹校验自动发现注释变了,自动更新字典,自动验证置信度是否回升。如果回升了,闭环完成;如果没回升(说明补的是假注释),债务重新进入 pending,下次通知里会标注「上次修复未生效」。
场景五:枚举值变更--硬暂停,不猜
数据库新增了 status = 'auto_approved'(自动通过)
用户:已通过的日报有多少?
AI 执行:
1. 定位表 -> 校验枚举指纹 -> 发现新值 auto_approved
2. 硬暂停,标记受影响口径为 stale
3. 输出确认问题:
- auto_approved 的业务含义是什么?
- 它应该算"已通过"还是单独一类?
- 是否需要更新"已通过"的统计逻辑?
4. 用户确认后 -> 更新字典和口径 -> 恢复问数
结构指纹检测不到枚举值变化--字段还在、类型没变、注释没变,但数据层多了一个新状态值。这是最危险的变更类型,因为结构指纹完全无感。枚举指纹专门解决这个问题,检测到新值就硬暂停,AI 不得猜测新枚举值的含义。
一张图看懂全貌
#mermaid-svg-yEGyQ8RIaKr02edq{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-yEGyQ8RIaKr02edq .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-yEGyQ8RIaKr02edq .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-yEGyQ8RIaKr02edq .error-icon{fill:#552222;}#mermaid-svg-yEGyQ8RIaKr02edq .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-yEGyQ8RIaKr02edq .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-yEGyQ8RIaKr02edq .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-yEGyQ8RIaKr02edq .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-yEGyQ8RIaKr02edq .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-yEGyQ8RIaKr02edq .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-yEGyQ8RIaKr02edq .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-yEGyQ8RIaKr02edq .marker{fill:#333333;stroke:#333333;}#mermaid-svg-yEGyQ8RIaKr02edq .marker.cross{stroke:#333333;}#mermaid-svg-yEGyQ8RIaKr02edq svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-yEGyQ8RIaKr02edq p{margin:0;}#mermaid-svg-yEGyQ8RIaKr02edq .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-yEGyQ8RIaKr02edq .cluster-label text{fill:#333;}#mermaid-svg-yEGyQ8RIaKr02edq .cluster-label span{color:#333;}#mermaid-svg-yEGyQ8RIaKr02edq .cluster-label span p{background-color:transparent;}#mermaid-svg-yEGyQ8RIaKr02edq .label text,#mermaid-svg-yEGyQ8RIaKr02edq span{fill:#333;color:#333;}#mermaid-svg-yEGyQ8RIaKr02edq .node rect,#mermaid-svg-yEGyQ8RIaKr02edq .node circle,#mermaid-svg-yEGyQ8RIaKr02edq .node ellipse,#mermaid-svg-yEGyQ8RIaKr02edq .node polygon,#mermaid-svg-yEGyQ8RIaKr02edq .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-yEGyQ8RIaKr02edq .rough-node .label text,#mermaid-svg-yEGyQ8RIaKr02edq .node .label text,#mermaid-svg-yEGyQ8RIaKr02edq .image-shape .label,#mermaid-svg-yEGyQ8RIaKr02edq .icon-shape .label{text-anchor:middle;}#mermaid-svg-yEGyQ8RIaKr02edq .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-yEGyQ8RIaKr02edq .rough-node .label,#mermaid-svg-yEGyQ8RIaKr02edq .node .label,#mermaid-svg-yEGyQ8RIaKr02edq .image-shape .label,#mermaid-svg-yEGyQ8RIaKr02edq .icon-shape .label{text-align:center;}#mermaid-svg-yEGyQ8RIaKr02edq .node.clickable{cursor:pointer;}#mermaid-svg-yEGyQ8RIaKr02edq .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-yEGyQ8RIaKr02edq .arrowheadPath{fill:#333333;}#mermaid-svg-yEGyQ8RIaKr02edq .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-yEGyQ8RIaKr02edq .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-yEGyQ8RIaKr02edq .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-yEGyQ8RIaKr02edq .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-yEGyQ8RIaKr02edq .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-yEGyQ8RIaKr02edq .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-yEGyQ8RIaKr02edq .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-yEGyQ8RIaKr02edq .cluster text{fill:#333;}#mermaid-svg-yEGyQ8RIaKr02edq .cluster span{color:#333;}#mermaid-svg-yEGyQ8RIaKr02edq div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-yEGyQ8RIaKr02edq .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-yEGyQ8RIaKr02edq rect.text{fill:none;stroke-width:0;}#mermaid-svg-yEGyQ8RIaKr02edq .icon-shape,#mermaid-svg-yEGyQ8RIaKr02edq .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-yEGyQ8RIaKr02edq .icon-shape p,#mermaid-svg-yEGyQ8RIaKr02edq .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-yEGyQ8RIaKr02edq .icon-shape .label rect,#mermaid-svg-yEGyQ8RIaKr02edq .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-yEGyQ8RIaKr02edq .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-yEGyQ8RIaKr02edq .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-yEGyQ8RIaKr02edq :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 持续优化阶段
日常问数阶段
接入阶段
识别数据源
就绪度评估
STOP-0.5
双路径建字典
确认口径
STOP-1
绑定指纹
指纹校验
候选值发现
SQL 安全检查
25 条规则
语义二次审查
只读执行
置信度评级
5 维度
答案 + 溯源
低置信度发现
债务写入
通知维护人员
维护人员补备注
指纹检测到变更
自动更新字典
验证置信度回升
闭环完成 ✅
三、架构内核:6 阶段状态机 + 三道 STOP 门
整个系统被设计为一个严格的 6 阶段状态机,每个阶段有明确的输入、输出和通过条件。三道 STOP 门穿插其中,任何一道不通过,流程立即暂停,不允许「蒙着眼睛往下走」。
#mermaid-svg-hbIRC0QjVcJDjPdN{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-hbIRC0QjVcJDjPdN .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-hbIRC0QjVcJDjPdN .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-hbIRC0QjVcJDjPdN .error-icon{fill:#552222;}#mermaid-svg-hbIRC0QjVcJDjPdN .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-hbIRC0QjVcJDjPdN .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-hbIRC0QjVcJDjPdN .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-hbIRC0QjVcJDjPdN .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-hbIRC0QjVcJDjPdN .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-hbIRC0QjVcJDjPdN .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-hbIRC0QjVcJDjPdN .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-hbIRC0QjVcJDjPdN .marker{fill:#333333;stroke:#333333;}#mermaid-svg-hbIRC0QjVcJDjPdN .marker.cross{stroke:#333333;}#mermaid-svg-hbIRC0QjVcJDjPdN svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-hbIRC0QjVcJDjPdN p{margin:0;}#mermaid-svg-hbIRC0QjVcJDjPdN .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-hbIRC0QjVcJDjPdN .cluster-label text{fill:#333;}#mermaid-svg-hbIRC0QjVcJDjPdN .cluster-label span{color:#333;}#mermaid-svg-hbIRC0QjVcJDjPdN .cluster-label span p{background-color:transparent;}#mermaid-svg-hbIRC0QjVcJDjPdN .label text,#mermaid-svg-hbIRC0QjVcJDjPdN span{fill:#333;color:#333;}#mermaid-svg-hbIRC0QjVcJDjPdN .node rect,#mermaid-svg-hbIRC0QjVcJDjPdN .node circle,#mermaid-svg-hbIRC0QjVcJDjPdN .node ellipse,#mermaid-svg-hbIRC0QjVcJDjPdN .node polygon,#mermaid-svg-hbIRC0QjVcJDjPdN .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-hbIRC0QjVcJDjPdN .rough-node .label text,#mermaid-svg-hbIRC0QjVcJDjPdN .node .label text,#mermaid-svg-hbIRC0QjVcJDjPdN .image-shape .label,#mermaid-svg-hbIRC0QjVcJDjPdN .icon-shape .label{text-anchor:middle;}#mermaid-svg-hbIRC0QjVcJDjPdN .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-hbIRC0QjVcJDjPdN .rough-node .label,#mermaid-svg-hbIRC0QjVcJDjPdN .node .label,#mermaid-svg-hbIRC0QjVcJDjPdN .image-shape .label,#mermaid-svg-hbIRC0QjVcJDjPdN .icon-shape .label{text-align:center;}#mermaid-svg-hbIRC0QjVcJDjPdN .node.clickable{cursor:pointer;}#mermaid-svg-hbIRC0QjVcJDjPdN .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-hbIRC0QjVcJDjPdN .arrowheadPath{fill:#333333;}#mermaid-svg-hbIRC0QjVcJDjPdN .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-hbIRC0QjVcJDjPdN .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-hbIRC0QjVcJDjPdN .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-hbIRC0QjVcJDjPdN .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-hbIRC0QjVcJDjPdN .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-hbIRC0QjVcJDjPdN .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-hbIRC0QjVcJDjPdN .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-hbIRC0QjVcJDjPdN .cluster text{fill:#333;}#mermaid-svg-hbIRC0QjVcJDjPdN .cluster span{color:#333;}#mermaid-svg-hbIRC0QjVcJDjPdN div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-hbIRC0QjVcJDjPdN .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-hbIRC0QjVcJDjPdN rect.text{fill:none;stroke-width:0;}#mermaid-svg-hbIRC0QjVcJDjPdN .icon-shape,#mermaid-svg-hbIRC0QjVcJDjPdN .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-hbIRC0QjVcJDjPdN .icon-shape p,#mermaid-svg-hbIRC0QjVcJDjPdN .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-hbIRC0QjVcJDjPdN .icon-shape .label rect,#mermaid-svg-hbIRC0QjVcJDjPdN .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-hbIRC0QjVcJDjPdN .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-hbIRC0QjVcJDjPdN .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-hbIRC0QjVcJDjPdN :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} STOP-0.5 不达标则暂停
STOP-1 未确认则暂停
STOP-2 槽位不全则追问
阶段 0 识别数据源 + 元数据后端
可选通知渠道配置
阶段 0.5 语义就绪度评估
6 维度评分 → S/A/B/C/D 级
阶段 1 建立实时结构证据层
大库走三层架构
阶段 2 确认业务口径
双路径:A 本地建字典 / B 输出待补充清单
阶段 3 写入确认口径 + 绑定三层指纹
阶段 4 问数前分层指纹校验
只校验涉及表 + 槽位补全
阶段 5 候选值发现 → SQL 安全检查 → 语义审查
→ 只读查询 → 置信度评级 → 答案+溯源 → 债务后处理
三道 STOP 门的设计意图
| STOP 门 | 位置 | 拦截什么 | 不放行的后果 |
|---|---|---|---|
| STOP-0.5 | 就绪度评估后 | 元数据质量不达标的库 | AI 在一堆看不懂的字段上硬猜,错误率失控 |
| STOP-1 | 建字典后 | 未经用户确认的业务口径 | 状态值含义、去重逻辑、时间范围全是猜的,结果不可信 |
| STOP-2 | 问数前 | 关键槽位不完整的查询 | 缺时间范围、缺统计口径,查出来的数字含义不明 |
这套状态机最反直觉的地方在于:它不追求「用户一问就答」。相反,它认为在口径未确认前,不回答比乱回答更好。Demo 可以接受 30% 的错误率,生产环境连 3% 都嫌多--而把错误率从 30% 压到 3% 的关键,恰恰是这些看似「拖慢流程」的 STOP 门。
四、语义就绪度评估:写 SQL 之前先给数据库打分
大多数 NL2SQL 系统默认假设「数据库的元数据是好的」,直接拿表结构和注释去生成 SQL。现实是,大量生产数据库的注释质量参差不齐--有的字段注释是空的,有的是把 varchar(255) 当注释,有的注释就是字段名的重复。
语义就绪度评估做的事很简单:在正式建字典之前,先花一轮对全库元数据做一个 6 维度量化评分,判断当前库的表、字段、关系、枚举等语义信息是否足够支撑 AI 准确理解业务。
6 维度评分体系
#mermaid-svg-3E8D7Qg4tzI8dYW2{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-3E8D7Qg4tzI8dYW2 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-3E8D7Qg4tzI8dYW2 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-3E8D7Qg4tzI8dYW2 .error-icon{fill:#552222;}#mermaid-svg-3E8D7Qg4tzI8dYW2 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-3E8D7Qg4tzI8dYW2 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-3E8D7Qg4tzI8dYW2 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-3E8D7Qg4tzI8dYW2 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-3E8D7Qg4tzI8dYW2 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-3E8D7Qg4tzI8dYW2 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-3E8D7Qg4tzI8dYW2 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-3E8D7Qg4tzI8dYW2 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-3E8D7Qg4tzI8dYW2 .marker.cross{stroke:#333333;}#mermaid-svg-3E8D7Qg4tzI8dYW2 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-3E8D7Qg4tzI8dYW2 p{margin:0;}#mermaid-svg-3E8D7Qg4tzI8dYW2 .pieCircle{stroke:#000000;stroke-width:2px;opacity:0.7;}#mermaid-svg-3E8D7Qg4tzI8dYW2 .pieOuterCircle{stroke:#000000;stroke-width:1px;fill:none;}#mermaid-svg-3E8D7Qg4tzI8dYW2 .pieTitleText{text-anchor:middle;font-size:25px;fill:#000000;font-family:"trebuchet ms",verdana,arial,sans-serif;}#mermaid-svg-3E8D7Qg4tzI8dYW2 .slice{font-family:"trebuchet ms",verdana,arial,sans-serif;fill:#000000;font-size:17px;}#mermaid-svg-3E8D7Qg4tzI8dYW2 .legend text{fill:#000000;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:17px;}#mermaid-svg-3E8D7Qg4tzI8dYW2 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 25%20%20%15%10%10%6 维度权重分配(满分 100) D2 字段级语义清晰度 D1 表级语义清晰度 D3 枚举/状态值清晰度 D4 表间关系清晰度 D5 命名规范一致性 D6 敏感字段标识度
| 维度 | 权重 | 核心问题 | 满分 |
|---|---|---|---|
| D1 表级语义清晰度 | 20% | 能不能一眼看出这张表是干什么的? | 20 |
| D2 字段级语义清晰度 | 25% | 每个字段含义是否明确、无歧义? | 25 |
| D3 枚举/状态值清晰度 | 20% | 状态/类型字段的取值含义是否可推断? | 20 |
| D4 表间关系清晰度 | 15% | 表与表怎么关联,能不能从元数据推断出来? | 15 |
| D5 命名规范一致性 | 10% | 命名风格是否统一、是否符合常识? | 10 |
| D6 敏感字段标识度 | 10% | 敏感字段是否有标记或可识别? | 10 |
权重分配本身就体现了工程经验:D2 字段级语义(25%)和 D3 枚举值清晰度(20%)合计占 45%--因为这两项是 NL2SQL 最容易出错的重灾区。字段含义模糊导致 JOIN 路径错误,枚举值含义不明导致 WHERE 条件完全错误。
等级与行动
#mermaid-svg-TDwrQjWwUDTHUPGF{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-TDwrQjWwUDTHUPGF .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-TDwrQjWwUDTHUPGF .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-TDwrQjWwUDTHUPGF .error-icon{fill:#552222;}#mermaid-svg-TDwrQjWwUDTHUPGF .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-TDwrQjWwUDTHUPGF .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-TDwrQjWwUDTHUPGF .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-TDwrQjWwUDTHUPGF .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-TDwrQjWwUDTHUPGF .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-TDwrQjWwUDTHUPGF .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-TDwrQjWwUDTHUPGF .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-TDwrQjWwUDTHUPGF .marker{fill:#333333;stroke:#333333;}#mermaid-svg-TDwrQjWwUDTHUPGF .marker.cross{stroke:#333333;}#mermaid-svg-TDwrQjWwUDTHUPGF svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-TDwrQjWwUDTHUPGF p{margin:0;}#mermaid-svg-TDwrQjWwUDTHUPGF .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-TDwrQjWwUDTHUPGF .cluster-label text{fill:#333;}#mermaid-svg-TDwrQjWwUDTHUPGF .cluster-label span{color:#333;}#mermaid-svg-TDwrQjWwUDTHUPGF .cluster-label span p{background-color:transparent;}#mermaid-svg-TDwrQjWwUDTHUPGF .label text,#mermaid-svg-TDwrQjWwUDTHUPGF span{fill:#333;color:#333;}#mermaid-svg-TDwrQjWwUDTHUPGF .node rect,#mermaid-svg-TDwrQjWwUDTHUPGF .node circle,#mermaid-svg-TDwrQjWwUDTHUPGF .node ellipse,#mermaid-svg-TDwrQjWwUDTHUPGF .node polygon,#mermaid-svg-TDwrQjWwUDTHUPGF .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-TDwrQjWwUDTHUPGF .rough-node .label text,#mermaid-svg-TDwrQjWwUDTHUPGF .node .label text,#mermaid-svg-TDwrQjWwUDTHUPGF .image-shape .label,#mermaid-svg-TDwrQjWwUDTHUPGF .icon-shape .label{text-anchor:middle;}#mermaid-svg-TDwrQjWwUDTHUPGF .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-TDwrQjWwUDTHUPGF .rough-node .label,#mermaid-svg-TDwrQjWwUDTHUPGF .node .label,#mermaid-svg-TDwrQjWwUDTHUPGF .image-shape .label,#mermaid-svg-TDwrQjWwUDTHUPGF .icon-shape .label{text-align:center;}#mermaid-svg-TDwrQjWwUDTHUPGF .node.clickable{cursor:pointer;}#mermaid-svg-TDwrQjWwUDTHUPGF .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-TDwrQjWwUDTHUPGF .arrowheadPath{fill:#333333;}#mermaid-svg-TDwrQjWwUDTHUPGF .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-TDwrQjWwUDTHUPGF .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-TDwrQjWwUDTHUPGF .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-TDwrQjWwUDTHUPGF .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-TDwrQjWwUDTHUPGF .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-TDwrQjWwUDTHUPGF .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-TDwrQjWwUDTHUPGF .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-TDwrQjWwUDTHUPGF .cluster text{fill:#333;}#mermaid-svg-TDwrQjWwUDTHUPGF .cluster span{color:#333;}#mermaid-svg-TDwrQjWwUDTHUPGF div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-TDwrQjWwUDTHUPGF .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-TDwrQjWwUDTHUPGF rect.text{fill:none;stroke-width:0;}#mermaid-svg-TDwrQjWwUDTHUPGF .icon-shape,#mermaid-svg-TDwrQjWwUDTHUPGF .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-TDwrQjWwUDTHUPGF .icon-shape p,#mermaid-svg-TDwrQjWwUDTHUPGF .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-TDwrQjWwUDTHUPGF .icon-shape .label rect,#mermaid-svg-TDwrQjWwUDTHUPGF .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-TDwrQjWwUDTHUPGF .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-TDwrQjWwUDTHUPGF .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-TDwrQjWwUDTHUPGF :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 🟢 S 级 85-100
直接建字典
进入阶段 1
🟡 A 级 70-84
建字典+补充
🟠 B 级 55-69
先补备注
维护人员补 COMMENT
重新评估
🔴 C 级 40-54
人工校准
⛔ D 级 0-39
全库重构
假注释识别:比没注释更危险的东西
评估体系里有一个容易被忽略但极其重要的设计--假注释识别。以下三种情况,该字段直接评 0 分:
- 注释 = 字段名重复 :
report_date的注释也是report_date - 注释 = 无意义描述 :
xxx/test/TODO/ 空字符串 - 注释 = 技术描述而非业务描述 :把
varchar(255)当注释
为什么假注释比没注释更危险?
没有注释时,AI 知道自己不知道,会走确认流程。但有假注释时,AI 会误以为「有注释就不用确认了」,直接基于错误理解生成 SQL--这是一种隐性的过度自信,比显式的猜测更难发现。把它直接评 0 分,强制走人工确认,是从源头切断这个风险。
双路径机制:S/A 级走 AI,B/C/D 级走人
#mermaid-svg-0SKc4TEhVusTLUV5{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-0SKc4TEhVusTLUV5 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-0SKc4TEhVusTLUV5 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-0SKc4TEhVusTLUV5 .error-icon{fill:#552222;}#mermaid-svg-0SKc4TEhVusTLUV5 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-0SKc4TEhVusTLUV5 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-0SKc4TEhVusTLUV5 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-0SKc4TEhVusTLUV5 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-0SKc4TEhVusTLUV5 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-0SKc4TEhVusTLUV5 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-0SKc4TEhVusTLUV5 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-0SKc4TEhVusTLUV5 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-0SKc4TEhVusTLUV5 .marker.cross{stroke:#333333;}#mermaid-svg-0SKc4TEhVusTLUV5 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-0SKc4TEhVusTLUV5 p{margin:0;}#mermaid-svg-0SKc4TEhVusTLUV5 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-0SKc4TEhVusTLUV5 .cluster-label text{fill:#333;}#mermaid-svg-0SKc4TEhVusTLUV5 .cluster-label span{color:#333;}#mermaid-svg-0SKc4TEhVusTLUV5 .cluster-label span p{background-color:transparent;}#mermaid-svg-0SKc4TEhVusTLUV5 .label text,#mermaid-svg-0SKc4TEhVusTLUV5 span{fill:#333;color:#333;}#mermaid-svg-0SKc4TEhVusTLUV5 .node rect,#mermaid-svg-0SKc4TEhVusTLUV5 .node circle,#mermaid-svg-0SKc4TEhVusTLUV5 .node ellipse,#mermaid-svg-0SKc4TEhVusTLUV5 .node polygon,#mermaid-svg-0SKc4TEhVusTLUV5 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-0SKc4TEhVusTLUV5 .rough-node .label text,#mermaid-svg-0SKc4TEhVusTLUV5 .node .label text,#mermaid-svg-0SKc4TEhVusTLUV5 .image-shape .label,#mermaid-svg-0SKc4TEhVusTLUV5 .icon-shape .label{text-anchor:middle;}#mermaid-svg-0SKc4TEhVusTLUV5 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-0SKc4TEhVusTLUV5 .rough-node .label,#mermaid-svg-0SKc4TEhVusTLUV5 .node .label,#mermaid-svg-0SKc4TEhVusTLUV5 .image-shape .label,#mermaid-svg-0SKc4TEhVusTLUV5 .icon-shape .label{text-align:center;}#mermaid-svg-0SKc4TEhVusTLUV5 .node.clickable{cursor:pointer;}#mermaid-svg-0SKc4TEhVusTLUV5 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-0SKc4TEhVusTLUV5 .arrowheadPath{fill:#333333;}#mermaid-svg-0SKc4TEhVusTLUV5 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-0SKc4TEhVusTLUV5 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-0SKc4TEhVusTLUV5 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-0SKc4TEhVusTLUV5 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-0SKc4TEhVusTLUV5 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-0SKc4TEhVusTLUV5 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-0SKc4TEhVusTLUV5 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-0SKc4TEhVusTLUV5 .cluster text{fill:#333;}#mermaid-svg-0SKc4TEhVusTLUV5 .cluster span{color:#333;}#mermaid-svg-0SKc4TEhVusTLUV5 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-0SKc4TEhVusTLUV5 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-0SKc4TEhVusTLUV5 rect.text{fill:none;stroke-width:0;}#mermaid-svg-0SKc4TEhVusTLUV5 .icon-shape,#mermaid-svg-0SKc4TEhVusTLUV5 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-0SKc4TEhVusTLUV5 .icon-shape p,#mermaid-svg-0SKc4TEhVusTLUV5 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-0SKc4TEhVusTLUV5 .icon-shape .label rect,#mermaid-svg-0SKc4TEhVusTLUV5 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-0SKc4TEhVusTLUV5 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-0SKc4TEhVusTLUV5 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-0SKc4TEhVusTLUV5 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} S/A 级
B/C/D 级
语义就绪度评估
等级判定
路径 A:AI 本地建字典草稿
→ 输出确认问题
→ 用户做判断题
→ 写入确认口径
路径 B:输出精确到表/字段的
待补充清单
→ 维护人员回库补 COMMENT
→ 重新评估
→ 达标后进入路径 A
进入阶段 3
两条路径可并行:AI 能看懂的字段走路径 A(写草稿 + 输出确认问题),AI 看不懂的字段走路径 B(输出待补充清单),两边都完成后进入阶段 3。
五、三层指纹同步:让字典和数据库永远对得上
确认口径写入了字典,但数据库是活的--字段会被增删,类型会被修改,枚举值会被新增。如果字典和数据库实际结构不一致,查询就会出错。
答案是 三层指纹,每一层检测不同类型的变更,走不同的处理流程:
| 指纹层 | 检测什么 | 校验方式 | 单表成本 |
|---|---|---|---|
| 结构指纹 | 字段增删改、类型变更、注释变更 | 一条 information_schema.columns 查询 + 本地算 hash |
< 10ms |
| 枚举指纹 | 状态/类型字段取值集合变化 | SELECT DISTINCT(大表采样)+ 算 hash |
< 100ms(有索引) |
| 行数估计 | 数据量异常变化 | information_schema.tables.table_rows |
< 10ms |
结构指纹的 SQL 实现,一条查询拿到一张表全部字段的定义,本地算 hash:
sql
-- MySQL 结构指纹计算
SELECT
CONCAT(
GROUP_CONCAT(
CONCAT(column_name, '|', column_type, '|',
IFNULL(column_comment, ''), '|', column_key)
ORDER BY ordinal_position
SEPARATOR '\n'
),
'|PK:', IFNULL((
SELECT GROUP_CONCAT(column_name ORDER BY seq_in_index)
FROM information_schema.statistics
WHERE table_schema = ? AND table_name = ?
AND index_name = 'PRIMARY'
), 'none')
) AS structure_text
FROM information_schema.columns
WHERE table_schema = ? AND table_name = ?;
-- 对 structure_text 计算 MD5/SHA256
为什么需要三层而不是一层
结构指纹检测不到枚举值变化--字段还在、类型没变、注释没变,但数据层多了一个新状态值。这是最危险的变更类型,因为结构指纹完全无感。
status 字段原来取值是 approved / pending / rejected / overdue_missing / rest_auto / exempt,现在新增了 auto_approved。如果 AI 不知道这个新值的存在,查询「已通过的日报」时可能漏算一大批自动通过的记录。
枚举值变更 = 硬暂停 :检测到枚举指纹变化时,系统立即硬暂停,标记受影响的口径为
stale,输出确认问题。AI 不得猜测新枚举值的含义。
变更处理矩阵
#mermaid-svg-xBliDpzYf4L8DFZy{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-xBliDpzYf4L8DFZy .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-xBliDpzYf4L8DFZy .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-xBliDpzYf4L8DFZy .error-icon{fill:#552222;}#mermaid-svg-xBliDpzYf4L8DFZy .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-xBliDpzYf4L8DFZy .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-xBliDpzYf4L8DFZy .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-xBliDpzYf4L8DFZy .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-xBliDpzYf4L8DFZy .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-xBliDpzYf4L8DFZy .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-xBliDpzYf4L8DFZy .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-xBliDpzYf4L8DFZy .marker{fill:#333333;stroke:#333333;}#mermaid-svg-xBliDpzYf4L8DFZy .marker.cross{stroke:#333333;}#mermaid-svg-xBliDpzYf4L8DFZy svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-xBliDpzYf4L8DFZy p{margin:0;}#mermaid-svg-xBliDpzYf4L8DFZy .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-xBliDpzYf4L8DFZy .cluster-label text{fill:#333;}#mermaid-svg-xBliDpzYf4L8DFZy .cluster-label span{color:#333;}#mermaid-svg-xBliDpzYf4L8DFZy .cluster-label span p{background-color:transparent;}#mermaid-svg-xBliDpzYf4L8DFZy .label text,#mermaid-svg-xBliDpzYf4L8DFZy span{fill:#333;color:#333;}#mermaid-svg-xBliDpzYf4L8DFZy .node rect,#mermaid-svg-xBliDpzYf4L8DFZy .node circle,#mermaid-svg-xBliDpzYf4L8DFZy .node ellipse,#mermaid-svg-xBliDpzYf4L8DFZy .node polygon,#mermaid-svg-xBliDpzYf4L8DFZy .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-xBliDpzYf4L8DFZy .rough-node .label text,#mermaid-svg-xBliDpzYf4L8DFZy .node .label text,#mermaid-svg-xBliDpzYf4L8DFZy .image-shape .label,#mermaid-svg-xBliDpzYf4L8DFZy .icon-shape .label{text-anchor:middle;}#mermaid-svg-xBliDpzYf4L8DFZy .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-xBliDpzYf4L8DFZy .rough-node .label,#mermaid-svg-xBliDpzYf4L8DFZy .node .label,#mermaid-svg-xBliDpzYf4L8DFZy .image-shape .label,#mermaid-svg-xBliDpzYf4L8DFZy .icon-shape .label{text-align:center;}#mermaid-svg-xBliDpzYf4L8DFZy .node.clickable{cursor:pointer;}#mermaid-svg-xBliDpzYf4L8DFZy .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-xBliDpzYf4L8DFZy .arrowheadPath{fill:#333333;}#mermaid-svg-xBliDpzYf4L8DFZy .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-xBliDpzYf4L8DFZy .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-xBliDpzYf4L8DFZy .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-xBliDpzYf4L8DFZy .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-xBliDpzYf4L8DFZy .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-xBliDpzYf4L8DFZy .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-xBliDpzYf4L8DFZy .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-xBliDpzYf4L8DFZy .cluster text{fill:#333;}#mermaid-svg-xBliDpzYf4L8DFZy .cluster span{color:#333;}#mermaid-svg-xBliDpzYf4L8DFZy div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-xBliDpzYf4L8DFZy .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-xBliDpzYf4L8DFZy rect.text{fill:none;stroke-width:0;}#mermaid-svg-xBliDpzYf4L8DFZy .icon-shape,#mermaid-svg-xBliDpzYf4L8DFZy .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-xBliDpzYf4L8DFZy .icon-shape p,#mermaid-svg-xBliDpzYf4L8DFZy .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-xBliDpzYf4L8DFZy .icon-shape .label rect,#mermaid-svg-xBliDpzYf4L8DFZy .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-xBliDpzYf4L8DFZy .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-xBliDpzYf4L8DFZy .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-xBliDpzYf4L8DFZy :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 全部一致
注释变好了
新增字段
字段被删
类型变了
枚举值新增
行数异常
指纹校验
比对结果
✅ 直接使用已确认口径查询
✅ 自动更新字典
继续查
📝 加入字典草稿
标记 auto_added
继续查
⚠️ 标记 deprecated
查询涉及时告知用户
⏸️ 标记 stale
提醒确认后查
⏸️ 硬暂停
输出确认问题
⚠️ 标注数据量异常变化
大表枚举采样
对无索引的大状态字段执行 SELECT DISTINCT 会导致全表扫描。系统采用采样策略:
sql
-- 大表枚举采样:不扫全表,只取最近 10000 行
SELECT DISTINCT status
FROM rep_daily_reports
WHERE id > (SELECT MAX(id) - 10000 FROM rep_daily_reports)
LIMIT 50;
单次问数(3 张表)的指纹校验成本约 9-15 条查询,总耗时 < 500ms。
六、SQL 安全检查与语义二次审查:两道独立闸门
SQL 生成后、执行前,必须通过两道独立闸门:确定性安全检查 (机械规则,约 25 条)和语义二次审查(以独立审查者视角检查 SQL 是否忠实于用户问题)。
第一道:25 条确定性安全检查
安全规则不能靠 AI 自觉,必须逐条机械检查。不通过不得执行,可自动修正的修正后重新检查。
| 检查类别 | 核心规则 | 不通过则 |
|---|---|---|
| 只读检查 | 只允许 SELECT,禁止 INSERT/UPDATE/DELETE/DDL/DCL/存储过程调用/递归 WITH | 拒绝执行 |
| 表范围检查 | FROM/JOIN 中的表必须在白名单内,不使用裸表名,不访问未确认/测试/历史表 | 拒绝执行 |
| 字段范围检查 | 所有字段在已加载表详情范围内,不用 SELECT *,敏感字段不在 SELECT 中 |
修正或拒绝 |
| 有界性检查 | 非聚合明细查询必须带 LIMIT,自动注入 LIMIT 201 作为溢出探针 |
自动修正 |
| 聚合合法性 | 非聚合列必须在 GROUP BY 中,不用 coalesce 把空集改写成 0 | 拒绝执行 |
| ClickHouse 专项 | 别名遮蔽检测:sum(x) AS x 违规,必须改用 computed_x |
拒绝执行 |
LIMIT 201 溢出探针 :非聚合明细查询自动注入 LIMIT 201 而不是 LIMIT 200。多出来的那 1 行是探针--如果结果返回了 201 行,说明实际数据超过 200 条被截断了,此时证据不完整,拒绝继续回答。
sql
-- AI 自动注入 LIMIT 201 作为溢出探针
SELECT report_date, staff_id, status, content
FROM rep_daily_reports
WHERE report_date = '2026-07-26'
LIMIT 201;
-- 返回 201 行 -> 截断 -> 拒绝回答,提示收窄范围
-- 返回 ≤ 200 行 -> 完整 -> 正常回答
ClickHouse 别名遮蔽 :ClickHouse 会在 WHERE、HAVING 和同层聚合表达式中全局替换 SELECT 别名。sum(x) AS x 会导致原本引用输入列 x 的地方变成引用聚合结果,产生语义错误。
sql
-- ❌ 违规:聚合函数遮蔽输入列
SELECT sum(x) AS x
SELECT CASE WHEN countIf(x IS NULL) = 0 THEN sum(x) ELSE NULL END AS x
-- ✅ 正确:直接透传允许
SELECT x AS x
SELECT t.x AS x
-- ✅ 正确:使用新别名不遮蔽
SELECT sum(x) AS computed_x
空集不用 coalesce 改写为 0 :聚合查询如果没有 GROUP BY 且涉及事件/流量指标,必须加 HAVING count() > 0,而不是用 coalesce(sum(x), 0) 把空集改写成 0。空集和零是两种不同的业务语义--「昨天没有任何日报提交记录」和「昨天日报提交指标的总和恰好是 0」是完全不同的情况。
第二道:语义二次审查
安全检查通过后,还要以独立审查者视角检查 SQL 是否忠实于用户问题和已确认口径:
- 问题覆盖:SQL 是否完整回答了用户问的问题,没有遗漏维度
- 口径匹配:使用的口径是否与已确认口径一致
- 过滤范围:WHERE 条件是否正确覆盖了用户要求的时间/组织/状态范围
- 粒度:GROUP BY 的粒度是否与用户期望一致
- JOIN:JOIN 路径是否正确,是否会导致行数翻倍
- NULL 处理:NULL 值是否被正确排除或包含
#mermaid-svg-zgKE84L5UlpLBBmZ{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-zgKE84L5UlpLBBmZ .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-zgKE84L5UlpLBBmZ .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-zgKE84L5UlpLBBmZ .error-icon{fill:#552222;}#mermaid-svg-zgKE84L5UlpLBBmZ .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-zgKE84L5UlpLBBmZ .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-zgKE84L5UlpLBBmZ .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-zgKE84L5UlpLBBmZ .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-zgKE84L5UlpLBBmZ .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-zgKE84L5UlpLBBmZ .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-zgKE84L5UlpLBBmZ .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-zgKE84L5UlpLBBmZ .marker{fill:#333333;stroke:#333333;}#mermaid-svg-zgKE84L5UlpLBBmZ .marker.cross{stroke:#333333;}#mermaid-svg-zgKE84L5UlpLBBmZ svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-zgKE84L5UlpLBBmZ p{margin:0;}#mermaid-svg-zgKE84L5UlpLBBmZ .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-zgKE84L5UlpLBBmZ .cluster-label text{fill:#333;}#mermaid-svg-zgKE84L5UlpLBBmZ .cluster-label span{color:#333;}#mermaid-svg-zgKE84L5UlpLBBmZ .cluster-label span p{background-color:transparent;}#mermaid-svg-zgKE84L5UlpLBBmZ .label text,#mermaid-svg-zgKE84L5UlpLBBmZ span{fill:#333;color:#333;}#mermaid-svg-zgKE84L5UlpLBBmZ .node rect,#mermaid-svg-zgKE84L5UlpLBBmZ .node circle,#mermaid-svg-zgKE84L5UlpLBBmZ .node ellipse,#mermaid-svg-zgKE84L5UlpLBBmZ .node polygon,#mermaid-svg-zgKE84L5UlpLBBmZ .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-zgKE84L5UlpLBBmZ .rough-node .label text,#mermaid-svg-zgKE84L5UlpLBBmZ .node .label text,#mermaid-svg-zgKE84L5UlpLBBmZ .image-shape .label,#mermaid-svg-zgKE84L5UlpLBBmZ .icon-shape .label{text-anchor:middle;}#mermaid-svg-zgKE84L5UlpLBBmZ .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-zgKE84L5UlpLBBmZ .rough-node .label,#mermaid-svg-zgKE84L5UlpLBBmZ .node .label,#mermaid-svg-zgKE84L5UlpLBBmZ .image-shape .label,#mermaid-svg-zgKE84L5UlpLBBmZ .icon-shape .label{text-align:center;}#mermaid-svg-zgKE84L5UlpLBBmZ .node.clickable{cursor:pointer;}#mermaid-svg-zgKE84L5UlpLBBmZ .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-zgKE84L5UlpLBBmZ .arrowheadPath{fill:#333333;}#mermaid-svg-zgKE84L5UlpLBBmZ .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-zgKE84L5UlpLBBmZ .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-zgKE84L5UlpLBBmZ .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-zgKE84L5UlpLBBmZ .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-zgKE84L5UlpLBBmZ .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-zgKE84L5UlpLBBmZ .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-zgKE84L5UlpLBBmZ .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-zgKE84L5UlpLBBmZ .cluster text{fill:#333;}#mermaid-svg-zgKE84L5UlpLBBmZ .cluster span{color:#333;}#mermaid-svg-zgKE84L5UlpLBBmZ div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-zgKE84L5UlpLBBmZ .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-zgKE84L5UlpLBBmZ rect.text{fill:none;stroke-width:0;}#mermaid-svg-zgKE84L5UlpLBBmZ .icon-shape,#mermaid-svg-zgKE84L5UlpLBBmZ .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-zgKE84L5UlpLBBmZ .icon-shape p,#mermaid-svg-zgKE84L5UlpLBBmZ .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-zgKE84L5UlpLBBmZ .icon-shape .label rect,#mermaid-svg-zgKE84L5UlpLBBmZ .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-zgKE84L5UlpLBBmZ .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-zgKE84L5UlpLBBmZ .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-zgKE84L5UlpLBBmZ :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} PASS
REVISE
再不通过
CLARIFY
BLOCKED
SQL 通过安全检查
语义二次审查
6 维度独立审查
审查结论
✅ 执行 SQL
修正 1 次后重新审查
❌ 结束当前查询
追问用户确认口径歧义
输出维护反馈
告知知识不足
七、置信度评级与债务闭环:让结果可信赖且自我修复
查询执行完了,结果也出来了,但这个结果有多可信?大多数 NL2SQL 系统到这里就结束了--给出一个数字,用户自己判断对不对。但这套架构认为:置信度不是 AI 的主观感觉,而是基于证据链的量化评分。
5 维度置信度评分
#mermaid-svg-SrQBGLaHvHzBnFmO{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-SrQBGLaHvHzBnFmO .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-SrQBGLaHvHzBnFmO .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-SrQBGLaHvHzBnFmO .error-icon{fill:#552222;}#mermaid-svg-SrQBGLaHvHzBnFmO .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-SrQBGLaHvHzBnFmO .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-SrQBGLaHvHzBnFmO .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-SrQBGLaHvHzBnFmO .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-SrQBGLaHvHzBnFmO .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-SrQBGLaHvHzBnFmO .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-SrQBGLaHvHzBnFmO .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-SrQBGLaHvHzBnFmO .marker{fill:#333333;stroke:#333333;}#mermaid-svg-SrQBGLaHvHzBnFmO .marker.cross{stroke:#333333;}#mermaid-svg-SrQBGLaHvHzBnFmO svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-SrQBGLaHvHzBnFmO p{margin:0;}#mermaid-svg-SrQBGLaHvHzBnFmO .pieCircle{stroke:#000000;stroke-width:2px;opacity:0.7;}#mermaid-svg-SrQBGLaHvHzBnFmO .pieOuterCircle{stroke:#000000;stroke-width:1px;fill:none;}#mermaid-svg-SrQBGLaHvHzBnFmO .pieTitleText{text-anchor:middle;font-size:25px;fill:#000000;font-family:"trebuchet ms",verdana,arial,sans-serif;}#mermaid-svg-SrQBGLaHvHzBnFmO .slice{font-family:"trebuchet ms",verdana,arial,sans-serif;fill:#000000;font-size:17px;}#mermaid-svg-SrQBGLaHvHzBnFmO .legend text{fill:#000000;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:17px;}#mermaid-svg-SrQBGLaHvHzBnFmO :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 30%20%20%15%15%5 维度置信度权重(满分 100) C1 口径确认度 C2 结构证据度 C3 指纹一致性 C4 数据完整性 C5 口径纯净度
| 维度 | 满分 | 评什么 |
|---|---|---|
| C1 口径确认度 | 30 | 使用的业务口径是否全部经过用户确认 |
| C2 结构证据度 | 20 | SQL 中每个表/字段是否有实时元数据证明 |
| C3 指纹一致性 | 20 | 涉及表是否通过三层指纹校验 |
| C4 数据完整性 | 15 | 是否受 LIMIT 截断、数据延迟、空值影响 |
| C5 口径纯净度 | 15 | 是否存在未确认的假设、草稿口径、猜测 |
C1 占 30 分(最高权重),因为口径错误是 NL2SQL 最致命的错误来源。一个状态值含义搞错,整个查询结果就全错了。
置信度评级结果直接呈现给用户:
markdown
## 置信度评估
综合置信度:🟢 92/100(S 级 - 高可信)
| 维度 | 得分 | 说明 |
|-----------------|--------|-----------------------------------------|
| C1 口径确认度 | 30/30 | 所有关键口径已确认 |
| C2 结构证据度 | 18/20 | 核心表本轮验证,1 字段依赖 2h 前快照 |
| C3 指纹一致性 | 20/20 | 三层指纹全部一致 |
| C4 数据完整性 | 14/15 | 数据最新到 2026-07-26,无截断 |
| C5 口径纯净度 | 10/15 | 1 个非关键假设:默认「最近」= 7 天 |
扣分项明细:
- C2 -2:字段 fill_tracking 依赖快照未本轮验证
- C4 -1:数据延迟 1 天(最新到 07-26,今天是 07-27)
- C5 -5:假设「最近」默认 7 天,未确认
展示规则 :B 级及以下必须显示完整 5 维度评分 + 扣分项明细 + 建议。不得省略置信度或把低置信度结果展示为高置信度。
置信度债务闭环:让系统自我修复
置信度计算完成不是结束,而是后处理的开始。低置信度问题会被聚合写入债务表,达到阈值后通知维护人员,修复后验证置信度回升,形成闭环。
#mermaid-svg-2tIML4ChD7zVtNJd{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-2tIML4ChD7zVtNJd .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-2tIML4ChD7zVtNJd .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-2tIML4ChD7zVtNJd .error-icon{fill:#552222;}#mermaid-svg-2tIML4ChD7zVtNJd .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-2tIML4ChD7zVtNJd .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-2tIML4ChD7zVtNJd .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-2tIML4ChD7zVtNJd .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-2tIML4ChD7zVtNJd .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-2tIML4ChD7zVtNJd .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-2tIML4ChD7zVtNJd .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-2tIML4ChD7zVtNJd .marker{fill:#333333;stroke:#333333;}#mermaid-svg-2tIML4ChD7zVtNJd .marker.cross{stroke:#333333;}#mermaid-svg-2tIML4ChD7zVtNJd svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-2tIML4ChD7zVtNJd p{margin:0;}#mermaid-svg-2tIML4ChD7zVtNJd defs #statediagram-barbEnd{fill:#333333;stroke:#333333;}#mermaid-svg-2tIML4ChD7zVtNJd g.stateGroup text{fill:#9370DB;stroke:none;font-size:10px;}#mermaid-svg-2tIML4ChD7zVtNJd g.stateGroup text{fill:#333;stroke:none;font-size:10px;}#mermaid-svg-2tIML4ChD7zVtNJd g.stateGroup .state-title{font-weight:bolder;fill:#131300;}#mermaid-svg-2tIML4ChD7zVtNJd g.stateGroup rect{fill:#ECECFF;stroke:#9370DB;}#mermaid-svg-2tIML4ChD7zVtNJd g.stateGroup line{stroke:#333333;stroke-width:1;}#mermaid-svg-2tIML4ChD7zVtNJd .transition{stroke:#333333;stroke-width:1;fill:none;}#mermaid-svg-2tIML4ChD7zVtNJd .stateGroup .composit{fill:white;border-bottom:1px;}#mermaid-svg-2tIML4ChD7zVtNJd .stateGroup .alt-composit{fill:#e0e0e0;border-bottom:1px;}#mermaid-svg-2tIML4ChD7zVtNJd .state-note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-2tIML4ChD7zVtNJd .state-note text{fill:black;stroke:none;font-size:10px;}#mermaid-svg-2tIML4ChD7zVtNJd .stateLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5;}#mermaid-svg-2tIML4ChD7zVtNJd .edgeLabel .label rect{fill:#ECECFF;opacity:0.5;}#mermaid-svg-2tIML4ChD7zVtNJd .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-2tIML4ChD7zVtNJd .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-2tIML4ChD7zVtNJd .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-2tIML4ChD7zVtNJd .edgeLabel .label text{fill:#333;}#mermaid-svg-2tIML4ChD7zVtNJd .label div .edgeLabel{color:#333;}#mermaid-svg-2tIML4ChD7zVtNJd .stateLabel text{fill:#131300;font-size:10px;font-weight:bold;}#mermaid-svg-2tIML4ChD7zVtNJd .node circle.state-start{fill:#333333;stroke:#333333;}#mermaid-svg-2tIML4ChD7zVtNJd .node .fork-join{fill:#333333;stroke:#333333;}#mermaid-svg-2tIML4ChD7zVtNJd .node circle.state-end{fill:#9370DB;stroke:white;stroke-width:1.5;}#mermaid-svg-2tIML4ChD7zVtNJd .end-state-inner{fill:white;stroke-width:1.5;}#mermaid-svg-2tIML4ChD7zVtNJd .node rect{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-2tIML4ChD7zVtNJd .node polygon{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-2tIML4ChD7zVtNJd #statediagram-barbEnd{fill:#333333;}#mermaid-svg-2tIML4ChD7zVtNJd .statediagram-cluster rect{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-2tIML4ChD7zVtNJd .cluster-label,#mermaid-svg-2tIML4ChD7zVtNJd .nodeLabel{color:#131300;}#mermaid-svg-2tIML4ChD7zVtNJd .statediagram-cluster rect.outer{rx:5px;ry:5px;}#mermaid-svg-2tIML4ChD7zVtNJd .statediagram-state .divider{stroke:#9370DB;}#mermaid-svg-2tIML4ChD7zVtNJd .statediagram-state .title-state{rx:5px;ry:5px;}#mermaid-svg-2tIML4ChD7zVtNJd .statediagram-cluster.statediagram-cluster .inner{fill:white;}#mermaid-svg-2tIML4ChD7zVtNJd .statediagram-cluster.statediagram-cluster-alt .inner{fill:#f0f0f0;}#mermaid-svg-2tIML4ChD7zVtNJd .statediagram-cluster .inner{rx:0;ry:0;}#mermaid-svg-2tIML4ChD7zVtNJd .statediagram-state rect.basic{rx:5px;ry:5px;}#mermaid-svg-2tIML4ChD7zVtNJd .statediagram-state rect.divider{stroke-dasharray:10,10;fill:#f0f0f0;}#mermaid-svg-2tIML4ChD7zVtNJd .note-edge{stroke-dasharray:5;}#mermaid-svg-2tIML4ChD7zVtNJd .statediagram-note rect{fill:#fff5ad;stroke:#aaaa33;stroke-width:1px;rx:0;ry:0;}#mermaid-svg-2tIML4ChD7zVtNJd .statediagram-note rect{fill:#fff5ad;stroke:#aaaa33;stroke-width:1px;rx:0;ry:0;}#mermaid-svg-2tIML4ChD7zVtNJd .statediagram-note text{fill:black;}#mermaid-svg-2tIML4ChD7zVtNJd .statediagram-note .nodeLabel{color:black;}#mermaid-svg-2tIML4ChD7zVtNJd .statediagram .edgeLabel{color:red;}#mermaid-svg-2tIML4ChD7zVtNJd #dependencyStart,#mermaid-svg-2tIML4ChD7zVtNJd #dependencyEnd{fill:#333333;stroke:#333333;stroke-width:1;}#mermaid-svg-2tIML4ChD7zVtNJd .statediagramTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-2tIML4ChD7zVtNJd :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 置信度<75\n未配置渠道
配置渠道后\n或达到阈值
影响次数≥2\n触发通知
维护人员收到通知\n开始修复
指纹检测到\n注释变更
下次查询置信度≥A级\n闭环完成 ✅
置信度仍
pending_unnotified
pending
notified
fixing
resolved
verified
整个闭环的核心机制:
- 债务写入:置信度 < 75 分时,按「表 + 字段 + 问题类型」聚合写入债务表,同一问题影响次数 +1
- 通知触发:影响次数 ≥ 2 且不在去重窗口内(24h 内已通知过不重复,72h 内给修复时间),触发飞书 webhook / 企微机器人通知
- 修复检测 :指纹校验检测到注释变更时,检查债务表,该表+字段有
notified状态的债务 -> 状态变resolved - 闭环验证 :下次查询置信度 ≥ A 级 -> 状态变
verified,闭环完成;置信度仍 < B 级 -> 债务重新进入pending,标注「上次修复未生效」
「上次修复未生效」检测 :维护人员补了注释但置信度没回升,说明补的是假注释或注释无效。系统会把这个债务重新标记为
pending,并在下次通知中标注「上次修复未生效」,避免维护人员以为问题已经解决了。
八、工程洞察:藏在细节里的设计权衡
候选值发现:用户说的不等于数据库里的
NL2SQL 最常见的错误来源是值猜测 。用户说「张三的日报」,AI 直接写 name = '张三'--但数据库里可能 name = '张三丰',真正的人名存在 nickname 字段,或者根本要用 staff_id = 'ZS001' 来过滤。
数据库 AI 系统 用户 数据库 AI 系统 用户 #mermaid-svg-OInYKaq6IyqGt0iL{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-OInYKaq6IyqGt0iL .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-OInYKaq6IyqGt0iL .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-OInYKaq6IyqGt0iL .error-icon{fill:#552222;}#mermaid-svg-OInYKaq6IyqGt0iL .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-OInYKaq6IyqGt0iL .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-OInYKaq6IyqGt0iL .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-OInYKaq6IyqGt0iL .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-OInYKaq6IyqGt0iL .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-OInYKaq6IyqGt0iL .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-OInYKaq6IyqGt0iL .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-OInYKaq6IyqGt0iL .marker{fill:#333333;stroke:#333333;}#mermaid-svg-OInYKaq6IyqGt0iL .marker.cross{stroke:#333333;}#mermaid-svg-OInYKaq6IyqGt0iL svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-OInYKaq6IyqGt0iL p{margin:0;}#mermaid-svg-OInYKaq6IyqGt0iL .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-OInYKaq6IyqGt0iL text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-OInYKaq6IyqGt0iL .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-OInYKaq6IyqGt0iL .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-OInYKaq6IyqGt0iL .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-OInYKaq6IyqGt0iL .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-OInYKaq6IyqGt0iL #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-OInYKaq6IyqGt0iL .sequenceNumber{fill:white;}#mermaid-svg-OInYKaq6IyqGt0iL #sequencenumber{fill:#333;}#mermaid-svg-OInYKaq6IyqGt0iL #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-OInYKaq6IyqGt0iL .messageText{fill:#333;stroke:none;}#mermaid-svg-OInYKaq6IyqGt0iL .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-OInYKaq6IyqGt0iL .labelText,#mermaid-svg-OInYKaq6IyqGt0iL .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-OInYKaq6IyqGt0iL .loopText,#mermaid-svg-OInYKaq6IyqGt0iL .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-OInYKaq6IyqGt0iL .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-OInYKaq6IyqGt0iL .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-OInYKaq6IyqGt0iL .noteText,#mermaid-svg-OInYKaq6IyqGt0iL .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-OInYKaq6IyqGt0iL .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-OInYKaq6IyqGt0iL .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-OInYKaq6IyqGt0iL .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-OInYKaq6IyqGt0iL .actorPopupMenu{position:absolute;}#mermaid-svg-OInYKaq6IyqGt0iL .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-OInYKaq6IyqGt0iL .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-OInYKaq6IyqGt0iL .actor-man circle,#mermaid-svg-OInYKaq6IyqGt0iL line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-OInYKaq6IyqGt0iL :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} "张三的日报"判断:张三 ≠ 数据库原值SELECT DISTINCT staff_id, nameFROM sys_usersWHERE name LIKE '%张三%' LIMIT 50('ZS001','张三'), ('ZS002','张三丰')找到 2 个匹配,请选择:1. 张三 (ZS001)2. 张三丰 (ZS002)选 1,张三锁定 staff_id='ZS001'SELECT * FROM rep_daily_reportsWHERE staff_id='ZS001'AND report_date='2026-07-26'查询结果返回答案 + 置信度
「双列候选」设计:当正式 SQL 需要稳定过滤键(如 staff_id),但用户看到的是展示名称(如 name)时,候选查询同时返回两列,用户选择展示名称,系统锁定对应的稳定键。只有字段注释或已确认语义明确指出展示字段与稳定过滤键的职责时才用双列--不能根据字段名猜测。
大库三层架构:1000+ 表不全库建字典
#mermaid-svg-nCYGWyMV5OnCIbdT{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-nCYGWyMV5OnCIbdT .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-nCYGWyMV5OnCIbdT .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-nCYGWyMV5OnCIbdT .error-icon{fill:#552222;}#mermaid-svg-nCYGWyMV5OnCIbdT .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-nCYGWyMV5OnCIbdT .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-nCYGWyMV5OnCIbdT .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-nCYGWyMV5OnCIbdT .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-nCYGWyMV5OnCIbdT .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-nCYGWyMV5OnCIbdT .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-nCYGWyMV5OnCIbdT .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-nCYGWyMV5OnCIbdT .marker{fill:#333333;stroke:#333333;}#mermaid-svg-nCYGWyMV5OnCIbdT .marker.cross{stroke:#333333;}#mermaid-svg-nCYGWyMV5OnCIbdT svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-nCYGWyMV5OnCIbdT p{margin:0;}#mermaid-svg-nCYGWyMV5OnCIbdT .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-nCYGWyMV5OnCIbdT .cluster-label text{fill:#333;}#mermaid-svg-nCYGWyMV5OnCIbdT .cluster-label span{color:#333;}#mermaid-svg-nCYGWyMV5OnCIbdT .cluster-label span p{background-color:transparent;}#mermaid-svg-nCYGWyMV5OnCIbdT .label text,#mermaid-svg-nCYGWyMV5OnCIbdT span{fill:#333;color:#333;}#mermaid-svg-nCYGWyMV5OnCIbdT .node rect,#mermaid-svg-nCYGWyMV5OnCIbdT .node circle,#mermaid-svg-nCYGWyMV5OnCIbdT .node ellipse,#mermaid-svg-nCYGWyMV5OnCIbdT .node polygon,#mermaid-svg-nCYGWyMV5OnCIbdT .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-nCYGWyMV5OnCIbdT .rough-node .label text,#mermaid-svg-nCYGWyMV5OnCIbdT .node .label text,#mermaid-svg-nCYGWyMV5OnCIbdT .image-shape .label,#mermaid-svg-nCYGWyMV5OnCIbdT .icon-shape .label{text-anchor:middle;}#mermaid-svg-nCYGWyMV5OnCIbdT .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-nCYGWyMV5OnCIbdT .rough-node .label,#mermaid-svg-nCYGWyMV5OnCIbdT .node .label,#mermaid-svg-nCYGWyMV5OnCIbdT .image-shape .label,#mermaid-svg-nCYGWyMV5OnCIbdT .icon-shape .label{text-align:center;}#mermaid-svg-nCYGWyMV5OnCIbdT .node.clickable{cursor:pointer;}#mermaid-svg-nCYGWyMV5OnCIbdT .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-nCYGWyMV5OnCIbdT .arrowheadPath{fill:#333333;}#mermaid-svg-nCYGWyMV5OnCIbdT .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-nCYGWyMV5OnCIbdT .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-nCYGWyMV5OnCIbdT .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-nCYGWyMV5OnCIbdT .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-nCYGWyMV5OnCIbdT .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-nCYGWyMV5OnCIbdT .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-nCYGWyMV5OnCIbdT .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-nCYGWyMV5OnCIbdT .cluster text{fill:#333;}#mermaid-svg-nCYGWyMV5OnCIbdT .cluster span{color:#333;}#mermaid-svg-nCYGWyMV5OnCIbdT div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-nCYGWyMV5OnCIbdT .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-nCYGWyMV5OnCIbdT rect.text{fill:none;stroke-width:0;}#mermaid-svg-nCYGWyMV5OnCIbdT .icon-shape,#mermaid-svg-nCYGWyMV5OnCIbdT .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-nCYGWyMV5OnCIbdT .icon-shape p,#mermaid-svg-nCYGWyMV5OnCIbdT .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-nCYGWyMV5OnCIbdT .icon-shape .label rect,#mermaid-svg-nCYGWyMV5OnCIbdT .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-nCYGWyMV5OnCIbdT .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-nCYGWyMV5OnCIbdT .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-nCYGWyMV5OnCIbdT :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 按需下钻
只查涉及表
第一层:表注册表(Table Registry)
━━━━━━━━━━━━━━━━━━━━━━━━━━━
全库轻量索引:表名 + 注释 + 模块标签 + 字段数 + 行数估计
一条 information_schema 查询即可获取,每次会话加载一次
第二层:模块语义包(Module Dictionary)
━━━━━━━━━━━━━━━━━━━━━━━━━━━
按业务模块分组,只对被用户问到的模块建完整字典
(字段/枚举/关系/口径)。非活跃模块不建字典,不占上下文
第三层:表级指纹校验(Per-table Fingerprint)
━━━━━━━━━━━━━━━━━━━━━━━━━━━
只校验当前查询涉及的 3-5 张表的指纹,不全库扫描
缓存 30 分钟,同模块连续查询复用结果
答案溯源:每个数字都可追溯
答案中每个数字必须可追溯到具体 SQL 和执行结果行。但溯源信息存文件(semantic_memory/query_traceability/),不直接展示给用户--给用户看的是用业务语言写的查询逻辑说明,用户看不懂的技术细节留存在溯源文件中。
用户看到的是:「从日报主表中筛选昨天日期的记录,按工号去重统计」。溯源文件里存的是:SELECT COUNT(DISTINCT staff_id) FROM rep_daily_reports WHERE report_date = '2026-07-26' AND status IN ('pending','approved','rejected') + 执行结果 105 行。两层信息各司其职,业务人员看得懂,技术人员查得到。
本地 md 只是快照,不是事实源
*_schema_analysis.md、*_nl2sql_dictionary.md 这些本地文件只是可选导出、审阅快照或离线 fallback,不是唯一事实源。最新性以实时元数据接口、schema hash 和确认口径版本为准。本地 md 会过时,但指纹校验每次都查实时元数据--如果本地字典说字段 A 存在但数据库已经删了,以数据库为准。
多数据库方言支持
| 数据库 | 元数据策略 | 备注 |
|---|---|---|
| MySQL | information_schema + SHOW | 默认方言 |
| ClickHouse | system.* 表 | 列式分析库,有专项安全规则(别名遮蔽) |
| Doris | MySQL 协议 + Doris 特性 | 兼容 MySQL 但不是纯 MySQL |
| PostgreSQL | information_schema + pg_catalog | 标识符大小写敏感 |
| Hive | Metastore / DESCRIBE | 离线数仓,分区裁剪关键 |
系统不得把 ClickHouse 或 MySQL 作为唯一 runtime 或唯一默认方言--这逼着架构从一开始就考虑方言差异,而不是先做 MySQL 再打补丁。
九、写在最后
回看这套架构,核心思想可以用三个词概括:证据、校验、闭环。
每一步都要有证据--结构证据来自实时元数据,业务口径来自用户确认,候选值来自 SELECT DISTINCT 查实际值。每一层都有校验--25 条安全检查 + 语义二次审查 + 三层指纹校验 + 置信度评级。每一个问题都有闭环--低置信度写入债务表,通知维护人员修复,指纹校验检测修复,置信度验证回升。
它不追求「用户一问就答」的丝滑体验,而是追求「每个答案都可信赖」的工程底线。在 Demo 里,30% 的错误率可以接受;在生产环境,一个错误数字可能引发错误决策。这两者之间的鸿沟,不是换一个更强的 LLM 就能填平的--它需要一套完整的工程架构来约束 AI 的行为。
这也是为什么这套架构里有 20 条硬规则、6 阶段状态机、3 道 STOP 门、3 层指纹、25 条安全检查、5 维度置信度评分。不是为了复杂而复杂,而是因为生产环境的现实就是这么复杂--如果你不把这些边界条件处理好,AI 就会在某个你没想到的角落翻车。
好的工程不是让 AI 更聪明,而是让 AI 在不聪明的时候也不会闯祸。这套架构的价值,不在于它能让 NL2SQL 多准,而在于它能让 NL2SQL 在不准的时候告诉你它不准--然后帮你把它修准。
架构速览:20 条硬规则 / 6 阶段状态机 / 3 道 STOP 门 / 3 层指纹 / 25 条安全检查 / 5 维度置信度
git地址:https://github.com/franx-0123/nl2sql (辛苦给个star哈)