SQL 报错别直接甩给模型:先剥掉 COPY 包装、再按根因分类的错误处理做法

本文提出面向数据Agent的SQL错误处理方法:先剥离COPY等协议包装,提取真实根因;再按模型可执行动作(如权限、类型转换、表缺失等)归类,而非字面匹配;最后为每类提供确定性修法文案,终局错误明确标注‘不要重试’。该方法显著降低模型试错次数,提升SQL执行稳定性与响应效率。

SQL 报错别直接甩给模型:先剥掉 COPY 包装、再按根因分类的错误处理做法

学习目标

读完这篇,你能给自己的数据 Agent 加一层错误处理:SQL 执行报错时,先把包在外面的协议包装剥掉、露出真正的根因,再按根因归类、给模型一句对症的修法。做完之后,模型不再对着一整段内层 SQL 猜「到底是权限问题还是表不存在」,权限终局的错不会再被重试,类型转换失败也不会再换三种写法连试三次。

先给大家科普一个概念「错误剥壳」。翻译成人话:很多数据库驱动报错时,会把真正的错误包在几层协议信息里,就像快递把小件塞进大箱子再套一层编织袋——模型看到的是最外层的袋子,根因藏在最里面。剥壳就是在把错误交给模型之前,先把外面的包装拆掉,只把根因递过去。

daw 就踩过这个坑。一轮真实复盘里,一次收支平衡点分析产生了 20 次工具错误,其中一类特别典型:Postgres 扩展的 COPY binary 协议错误,把根因埋在整段内层 SQL 之后,模型要靠 6 次试错才解读出权限、表不存在、类型转换失败这三类根因。修法不是让模型更聪明,而是让错误处理先把根因挖出来。

1. 前置知识:错误到模型手里之前要过几道手

动手之前,先把 daw 里一条 SQL 执行错误的旅程说清楚。模型调 execute_query 发 SQL,远端执行失败,驱动返回一段错误文本;这段文本先经过两道处理,才会出现在模型的工具结果里:

第一道是剥壳(decapsulate_copy_error):把 COPY 协议包装拆掉,露出根因。第二道是分类加文案(classify_sql_error_class + sql_error_advice):按根因归类,再配一句对症的修法建议。

为什么需要两道?因为「露出根因」和「知道怎么修」是两件事。剥壳只解决「错在哪」,分类和文案解决「该怎么办」——权限终局的错要告诉模型别重试,类型转换的错要告诉模型换哪种写法。两道都缺一不可:只剥壳不给修法,模型还是要猜;只给文案不剥壳,文案根本匹配不上被埋住的根因。

还有一个关键点:这两道处理接在 execute_query 和 render_chart 两条错误路径上共用。文案链曾经在两个工具里各自生长,结果同一类错误在查数时一种说法、在画图时另一种说法——后来统一收到 sql_error_advice 一个函数里,新错误形状只改这一个地方。翻译成人话:错误文案必须有唯一出口,否则改一处漏一处,模型收到的建议迟早自相矛盾。

2. 分步骤实操

步骤 1:先收集真实错误原文,再写剥壳规则

写剥壳规则之前,先别凭想象编错误格式。daw 的做法是:从复盘实录里把真实错误原文原样抄进单测,再针对原文写规则。那次复盘里 COPY 包装有两种形态:

  • prepare 形态:Failed to prepare COPY "<整段 SQL>"...,根因跟在整段 SQL 之后;
    • fetch-header 形态:Failed to fetch header for COPY ...,根因跟在协议描述之后。
      剥壳规则就两条,对应这两种形态。prepare 形态:找到最后一个 ;": 标记,确认前面确实有 Failed to prepare COPY "——这个确认不能省,否则一段普通错误里恰好含有相同字符序列会被误剥。fetch-header 形态:按 Failed to fetch header for COPY 切开,取后半段。两种都不匹配,原样返回,不动它。

剥完之后还要处理尾巴格式:COPY 错误尾有 ;": ERROR: xxx 和 : ERROR: xxx 两种,剥掉可选的冒号,再剥掉 ERROR: 标签,最后只剩根因本身。比如权限那条,剥完就剩下 check permission for foreign table scan failed: failed to check permission:MaxCompute——模型一眼就能看懂,不用再翻过整段 SQL 去找。

步骤 2:按根因归类,而不是按报错字面归类

剥出根因之后,下一步是分类。分类函数 classify_sql_error_class 的值域是固定的几个类:权限、超时、存储形态、资源超限、类型不支持、列缺失、函数缺失、运算符不匹配、表缺失、类型转换、语法、其他。固定值域的好处是:日志表和对话记录都能按这个维度过滤,事后复盘可以直接统计「这个月权限类错误占几成」,而不是对着自由文本做词频。

分类的顺序有讲究,写的时候要按「最容易误判的优先」。举两个 daw 复盘里真实踩过的坑:

第一,「函数不存在」必须排在「表不存在」之前。Hologres 没有 hex(text) 时报的是函数不存在,但词面里同样含有 does not exist——如果先匹配「不存在」就归进表缺失,模型会被诱导去重新注册一张明明存在的表,错上加错。

第二,「列不存在」必须单独成类,不能并进「表不存在」。列名写错(比如把 payment_time 写成 pay_time)时,报错词面也是「不存在」;如果归进表缺失,模型会去怀疑整张表,而正确修法只是用 describe_table 查真实列名再改 SQL。

翻译成人话:分类不是按报错里出现了哪个词,而是按「模型下一步该干什么」来分。下一步动作不同的错误,必须是不同的类;下一步动作相同的错误,才可以并成一类。

步骤 3:每个类配一句对症修法,终局的要明说「不要重试」

分类之后,每个类配一句文案,格式固定为三段:「查询失败:<定性>。错误:<剥壳原文>」+「建议:<对症修法>」。定性让模型知道错的性质,剥壳原文保留证据,建议告诉它下一步。

文案里最重要的一类词是「终局」。权限类和存储形态类就是终局:MaxCompute 外表权限没开通,换写法、换参数、重试多少次都不会过;非标准存储的表远端不支持,同样绕不过去。文案必须明说「属终局,不要重试此表」,并且给出替代动作:检查连接用户有没有该表权限,或者用 list_remote_tables 一次列全同类表、请用户批量开通,避免逐表撞权限墙。

类型转换类则相反,是可以修的,但要给准写法。Hologres 的字符串转数值有三条实测规则:AS INT 遇到 "2000.0" 这种带小数的文本会失败,AS DOUBLE 远端不支持,能用的是 ::FLOAT8 或 ::numeric。文案把这三条直接写出来,模型就不用连试三次才收敛——那次复盘里模型就是这样试了三次才找到能用的写法。

资源超限类也要给准动作:缩小扫描范围(加时间或分区过滤,事件类大表先裁 ds 分区)、先聚合再去重、避免大宽表直接 JOIN;确需明细就分批取。关键是点明「这不是语法问题,同形状重试大概率复现」——否则模型会把资源问题当成 SQL 写错了,在原地改写法打转。

步骤 4:新错误形状只改分类和文案两个地方

错误处理层最怕的就是越补越散。daw 定了一条规矩:新错误形状出现时,只改 classify_sql_error_class 和 sql_error_advice 两个地方,执行路径(execute_query、render_chart)不跟着改——它们只管在错误出口调用「剥壳 + 文案」这一条链。

这次新增的两个类就是按这条规矩落地的:权限类覆盖了 check permission(此前只认 permission denied 和 access denied,MaxCompute 外表权限的报错会漏网),类型转换类新增了 cast_error。同时,知识库里沉淀了一条 Hologres SQL 兼容排障配方,让同类问题下次有据可查。翻译成人话:错误处理层也要有「唯一出口」和「固定值域」,否则每补一个新形状就多一处分叉,三个月后没人敢动。

步骤 5:单测用真实错误原文,不用编造的报错

最后一步是测试。剥壳和分类的单测全部取自复盘会话的真实错误原文——比如那条带整段内层 SQL 的 COPY 权限错误,断言剥完之后确实含有 check permission;fetch-header 形态的 CAST 错误,断言分类确实落在 cast_error。用真实原文的好处是:规则改动时,测试测的是真实世界会遇到的形状,而不是自己编出来、刚好能过的形状。

3. 为什么这样设计

这套设计背后就一句话:模型看到的错误,应该是「根因 + 修法」,而不是「原始报错全文」。原始报错是给驱动开发者看的,里面有协议细节、有内层 SQL、有堆栈信息;模型不需要这些,它需要的是两样东西:错在哪一类、下一步干什么。剥壳解决第一样,分类和文案解决第二样。

更深一层说,这是在给模型的试错成本设上限。没有这层处理时,模型靠 6 次试错解读一类错误、靠 3 次试错收敛一种写法——每次试错都是一次远端查询、一次 token 消耗、一次用户等待。错误处理层把「解读」从模型手里收回来,变成确定性的代码,试错次数自然就降下来了。这和思维导图工具「模型给结构、代码管布局」是同一个思路:确定性的活,别让模型用概率去猜。

4. 避坑清单

  • 不要把原始报错直接甩给模型。包在协议里的根因,模型要靠多次试错才能挖出来,而试错的成本是真实的查询和等待。
    • 剥壳规则必须带形态确认。只凭一个分隔符就切,普通错误里恰好含有相同字符时会被误剥;先确认是 COPY 包装,再动手切。
    • 分类顺序按误判风险排。函数缺失、列缺失这类词面撞车的,必须排在表缺失之前,否则下一步动作全错。
    • 终局类错误必须明说「不要重试」。不说这句话,模型默认会重试——而终局错误的重试除了烧 token 和等待,什么都得不到。
    • 文案要给准写法,不要只给方向。「换种转换方式」是正确的废话;「用 ::FLOAT8 或 ::numeric,别用 AS INT 和 AS DOUBLE」才是能直接执行的修法。
    • 错误文案必须有唯一出口。两个工具各自长文案分支,迟早自相矛盾;新形状只改分类和文案两个地方。
    • 单测用真实错误原文。用编造的报错写测试,等于只测了自己想象中的世界。

5. 总结

给数据 Agent 做错误处理,记住这条链:剥壳 → 分类 → 对症文案。剥壳把根因从协议包装里挖出来,分类按「下一步该干什么」归类,文案给一句能直接执行的修法、终局的明说不要重试。按这条链做,模型解读一类错误不再需要 6 次试错,收敛一种写法不再需要连试三次——而这些省下来的轮次,就是用户少等的每一秒。

给开发者的建议:下次你的 Agent 在同一个错误上反复试错时,先别调提示词,先把那条真实错误原文抄下来,看看根因是不是被包在了什么东西里。如果是,剥壳和分类的代码,比任何提示词都更能立竿见影。

项目开源在 GitHub:tsingliuwin/daw,觉得有用欢迎点个 star。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值