← 数据分析 Agent

为什么安全校验不通过时不纠错,纠错又只纠一次?

困难 SQL 执行失败自动纠错 · 第 2 / 2 问 更新于 2026/09/27
Text-to-SQLSQL纠错重试策略数据分析Agent
学习 AI 实战项目

简化版

因为两种失败的性质不同。安全校验不通过,说明写法本身不合规(查了白名单外的表、带危险关键字、写了注释),让模型再答一遍多半还是同一类毛病,白花一次调用;更重要的是,如果让模型「修到能过校验」,等于教它绕过安全规则。执行报错才是「写法合规、但在这个库里查不通」,有数据库报错作为精确反馈,才值得交给模型改一版。纠错只纠一次,是因为一次纠错能解决的主要是字段名、分组这类简单错误;一次没改好多半是理解层面的问题,继续重试成本高、收益低,还可能来回震荡。

详细版

失败分类与处理策略:

失败类型典型原因处理
安全校验失败越权查表、危险关键字、注释、多语句停在「校验失败」,记录原因,不纠错
执行报错字段不存在、分组不完整、函数不兼容纠错一次,重新校验执行
纠错后仍报错理解偏差、元数据缺失停止,展示最终报错,交给人处理
权限、超时、连接环境问题不纠错,提示用户或运维

重试的一般原则:只重试「换个做法可能成功」的失败,并且每次重试都要带上新的信息。同样的输入重试同样的步骤,结果大概率不变。

完整版教学

一、失败要先分类,再决定要不要重试

很多系统对失败的处理是「一律重试 N 次」,这在调用外部服务时可能合理(网络抖动),但在 Text-to-SQL 里很浪费。Text-to-SQL 的失败可以分成几类,每一类要回答一个问题:再让模型试一次,有没有新的信息能让它做对?

校验失败    新信息:只有「违反了哪条规则」
执行报错    新信息:数据库给出的具体错误位置
纠错后仍错  新信息:又一条报错,但模型已经看过一次同类信息
环境问题    新信息:与 SQL 写法无关

只有执行报错这一类,重试时带进去的信息足够具体,值得交给模型改。

二、校验失败为什么不纠错

有人会觉得:校验不过就把原因告诉模型,让它改到能过不就行了?这里有两层问题。

第一层是效率。 校验失败通常说明模型对「能查什么」的理解本身有偏差,比如问题涉及的数据本来就不在允许范围内。再让它答一遍,它大概率还会用同样的表,或者换一张同样不允许的表。

第二层是安全,更关键。 如果系统的设计是「校验失败 → 把失败原因告诉模型 → 让它改到通过」,就等于在教模型绕过安全规则:

第 1 次:select ... from salary_detail        → 不允许查询表 salary_detail
第 2 次:select ... from hr.salary_detail     → 换了写法试探
第 3 次:select ... from salary_detail_view   → 继续试探

安全校验是边界,边界的正确反馈是「拒绝并告知用户」,而不是「提示怎么绕过去」。遇到校验失败,更合理的做法是告诉用户这个问题超出了当前数据集的范围。

记忆钩子:安全校验是边界,不是考题。边界不给提示,也不给重考。

三、纠错为什么只纠一次

执行报错交给模型纠错,第一次通常很有效:字段写错、别名用错、分组不完整,报错信息把问题指得很清楚。但如果纠错后还报错,情况就不同了:

情况说明再纠一次的期望
同一个错误模型没理解报错,或上下文里缺少关键信息很低
新的错误改 A 引入了 B可能继续引入 C,出现震荡
能跑但结果为空过滤条件被改坏无报错可纠

每多纠一次就多一次模型调用和一次数据库执行,而成功率快速下降。所以设一个硬上限——自动纠错一次——把有限的调用预算花在最可能成功的那一次上。要继续尝试,应该从头重新生成 SQL,而不是在一条已经被改过的 SQL 上反复修补。

四、「换个做法」比「再试一次」有效

同一个思路可以推广到整个问数系统的降级设计:

固定口径工具失败 → 换成 Text-to-SQL 兜底再试一次(换了一种做法)
Text-to-SQL 执行报错 → 纠错一次(带着新信息)
都失败 → 停下,记录停在哪一步、为什么

这里没有任何「原样再试一次」的环节。原样重试适合网络超时这类偶发故障,不适合模型推理这类确定性的失败:同样的输入给同一个模型,结果大概率一样。

五、纠错要留原件

纠错会覆盖原来的 SQL。覆盖之前,把现场保存下来:

original_sql       纠错前的 SQL
original_error     数据库返回的原始报错
corrected_sql      模型改出来的 SQL
correction_reason  模型给出的修改理由

这样事后回看,能完整还原「最初写成什么样、因为什么报错、改成了什么」。如果允许用户再次纠错,应该要求先重新生成 SQL,并清掉上一轮的纠错痕迹,避免两轮的原件和修正混在一起。

六、用数据验证策略是否合理

「只纠一次」不是拍脑袋,可以用两个指标持续观察:

指标计算说明
纠错触发率触发纠错的次数 ÷ 执行次数高了说明生成环节的上下文或元数据有问题
纠错成功率纠错后执行成功的次数 ÷ 纠错次数如果很高,说明一次纠错的性价比很好

举个例子:100 次执行中有 15 次触发纠错,其中 12 次一次就改好,成功率 80%;剩下 3 次如果再纠一次,假设能再救回 1 次,就是多花 3 次模型调用换 1 次成功。这类数据能帮你判断上限设在 1 还是 2 更合适。触发率本身更值得盯:它高的时候,与其在纠错上加次数,不如回头补字段语义和表关系。

七、把规则写成代码,而不是写在 Prompt 里

「校验失败不纠错」「纠错只一次」这些规则,要由代码的状态机决定,而不是在 Prompt 里告诉模型「最多尝试一次」。模型不负责控制流程,它只负责在被调用时给出一个修正方案;调用几次、什么情况下调用,由代码根据记录的状态判断。这样规则可靠、可测试,也不会因为换了模型而失效。

八、常见误区与追问

  • 误区:失败了就让模型多试几次,总能成功。 同样的输入重试,结果大概率不变;只有带着新信息、或者换一种做法的重试才有效。
  • 误区:安全校验失败时把原因告诉模型,让它改到能通过。 这等于教模型绕过安全边界,校验失败应该拒绝并告知用户。
  • 误区:纠错次数设得越多,成功率越高。 第二次以后的成功率快速下降,还可能在不同错误之间震荡,成本却线性增加。
  • 误区:纠错直接覆盖原 SQL 就行。 要保留原 SQL 和原报错,否则无法回看纠错过程,也分不清两轮纠错的结果。
  • 误区:重试次数写在 Prompt 里让模型自己控制。 流程控制要由代码的状态判断,模型只负责给出修正方案。
  • 追问:纠错后仍然失败,用户想再试怎么办? 要求重新生成 SQL,从头走一遍,并清掉上一轮纠错痕迹,而不是在修改过的 SQL 上继续修补。
  • 追问:怎么判断纠错上限设成一次是否合适? 看纠错触发率和纠错成功率,用数据评估多纠一次的收益和成本。

九、加强记忆

重试之前先分类,只重试「带着新信息或换个做法可能成功」的失败。安全校验失败不纠错:一是写法本身不合规,重答多半还是同样的问题;二是让模型改到能过等于教它绕过边界,边界不给提示也不给重考。执行报错有数据库的精确反馈,才值得纠错,而且只纠一次,第二次以后成功率骤降、成本线性增加还可能震荡,想再试就重新生成。纠错前保留原 SQL 和原报错,规则由代码的状态机控制而不是写在 Prompt 里,用纠错触发率和成功率验证策略,触发率高就回头补元数据。