ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Text-to-SQL Agent三大核心环节:执行前、中、后管控

Text-to-SQL Agent三大核心环节:执行前、中、后管控 Text-to-SQL Agent除了生成 SQL还得管这三件事做 Text-to-SQL 做了两年多从最早纯靠提示词调大模型到后面自己搭完整的 Agent 链路最大的感受是SQL 生成只是入场券。今天大部分团队卡住的不是“模型能不能写出一条正确的 SELECT”而是 SQL 生成之后那一堆破事——权限、数据血缘、执行安全、结果解释、失败恢复。标题里说的“三件事”其实就是我在生产环境里被反复教育出来的三个核心环节执行前拦截、执行中管控、执行后反馈。这篇就把完整的拆解思路和落地细节写出来希望能让正在做 Agent 开发的同行少踩几个坑。先说清楚本文适合谁正在设计或维护 Text-to-SQL Agent 的工程师、负责数据平台接入的交付同学以及想搞清楚“为什么别人的 Agent 演示那么顺、一上生产就拉胯”的产品负责人。我会按实际落地顺序来讲不绕弯子。1. 整体设计思路把“生成 SQL”降级为中间步骤很多团队拿到需求第一反应是“我要让大模型生成 SQL”然后疯狂调 prompt、换模型、做 few-shot。我早期也是这样直到某天线上出了一个低级事故模型生成的 SQL 把一张全量事实表的过滤条件写漏了直接导致下游报表跑了三小时重算。从那之后我彻底改了一个认知Text-to-SQL Agent 的核心不是语言模型而是围绕 SQL 的管控闭环。1.1 核心需求解析站在用户视角自然语言问“上个月华东区各品类销售额”需求链条其实有三层第一层把自然语言翻译成可执行的 SQL这是模型干的活第二层确认这条 SQL 是否安全、是否可解释、是否真能跑通这是系统该干的活第三层把执行结果用用户能看懂的方式回传并处理失败与异常这是工程该干的活。大多数开源 demo 只做了第一层所以我强调“生成只是入场券”。而本文说的“三件事”本质是把第二、三层显式拆出来作为 Agent 的三个核心模块权限与语义校验Pre-execution、执行沙箱与熔断Execution、结果解释与反馈Post-execution。这三个模块加在一起才是一个 Agent 开发闭环。1.2 为什么必须管这三件事直接原因就三条数据库是生产资产SQL 一旦执行可能产生全表扫描、锁竞争、甚至误删数据。大模型现在写复杂 SQL 的正确率远没到能裸奔的程度必须有防护层。用户要的不是 SQL 而是答案如果返回的是“执行成功、影响 100 行”用户依旧不知道这 100 行意味着什么Agent 得负责把结果转成自然语言或图表结论。故障是常态表结构变更、字段改名、权限漂移、模型幻觉任何一个环节出错都会让链路中断。没有反馈机制Agent 就成了哑巴工具。用生活类比来说Text-to-SQL Agent 像一个新来的数据分析师。分析师的 SQL 写得再快如果他不知道哪些表能碰、跑挂了不会恢复、说不出结果背后的含义你也不敢把生产库交给他。所以“三件事”本质上是给这个虚拟分析师配了三个 supervisor一个管事前审批一个管事中监督一个管事后复盘。2. 执行前拦截权限、表结构校验与语义安全这一节是“三件事”里的第一件。我把它放在最前面因为所有事故里事前拦截能拦住的占六七成。模型生成 SQL 后不能直接丢给数据库要先过三道检查。2.1 权限校验最小权限原则落到 SQL 粒度很多系统权限控制是“表级”的——你能读 sales 表不能读 salary 表。但实际生产里粒度必须更细同一个表里可能有敏感列用户手机号、身份证、可能有按分区限定的数据范围只能看本部门。SQL 粒度权限的意思就是解析出 SQL 引用的库、表、列、分区再和权限图谱比对。具体做法不复杂用 SQL 解析器Python 生态推荐 sqlglotJava 生态可以选 JSqlParser把生成 SQL 解析成 AST从 AST 提取SELECT、FROM、JOIN、WHERE、GROUP BY涉及的库表列将这些引用与“用户-角色-表-列-行级策略”的权限矩阵做交集判断任意一项越权直接返回“SQL 生成失败原因无权访问 xxxx”不给执行机会。这里有三个细节值得注意一是通配符*必须展开后再校验否则SELECT *会绕过列级权限二是子查询和 CTE 内部的引用也要递归解析只查顶层 FROM 是漏的三是函数调用比如COUNT(phone)要能从函数参数里识别列引用。我见过好几起事故都是因为只看顶层导致子查询裸奔。提示权限校验组件最好独立部署成服务通过 gRPC 或 HTTP 提供给 Agent 调用不要和大模型推理进程混在一起。权限策略变更时才能独立热更新也避免一方故障拖垮另一方。2.2 表结构校验让 SQL 落在真实 schema 上模型幻觉不仅体现在 SQL 语法更多体现在“表名编造”和“列名编造”。比如模型觉得用户问“销售额”就该有total_sales列但真实表里叫sales_amount。处理方式就是拿 schema 信息强制约束生成阶段和校验阶段。生成阶段的做法把相关表的 schema 摘要表名、列名、列类型、注释、主外键、分区键作为上下文注入 prompt。但注意schema 不能全量塞一张宽表几百列塞进去既浪费 token 又分散注意力。我常用的策略是先做一个轻量“列选择器”让模型先产出候选表和候选列再基于候选列表检 schema 元数据或者用 embedding 召回把列名、注释做向量化根据用户问题召回 topN 列。校验阶段则简单粗暴执行前把 SQL AST 里的每个表名列名逐个到 schema 注册表里去查查不到的直接打回。这一步我建议做成硬校验不要给模型“编一个相似列”的机会。宁可告诉用户“查无此列”也不要让 Agent 自由发挥否则错误成本太高。2.3 语义安全识别危险模式与成本预估权限和表结构校验通过不代表 SQL 就安全。还得看语义层面是否可能全表扫描SELECT *加无过滤条件的大表查询预估扫描行数是否超过阈值是否会造成锁竞争事务里对大表执行UPDATE/DELETE或未带WHERE的更新是否属于危险操作DROP、TRUNCATE、ALTER一律拦截除非有专人审批流程是否符合成本预算通过表的统计信息行数、平均行宽估算扫描量超出配额直接拒绝或询问用户。这部分的实现很多团队会忽略觉得“模型不会生成删库语句吧”。实测下来在 agent 多步推理的场景里模型为了完成“把这批数据清理掉”这样的用户指令真的有可能生成 DROP 或不带 WHERE 的 DELETE。再加上 Text-to-SQL 链路里如果有“自动执行上一步 SQL”的设定风险成倍放大。所以语义安全校验不能省。3. 执行中管控沙箱、超时、熔断与观察性第二件事是执行过程管控。事前校验得再严总有漏网之鱼网络抖动、数据库负载飙升、慢 SQL 卡死连接这些都要在执行层处理。我把这节拆成四个维度。3.1 执行沙箱能读不能写、能预览不落库如果条件允许Text-to-SQL Agent 最好接入独立的只读副本或分析型数仓而不是直连业务主库。原因很现实生成 SQL 的执行计划不可控你无法保证每次都是完美索引命中的轻量查询。沙箱的核心是最小副作用连接只读账号SELECT以外的语句直接被数据库权限拒绝如果必须读写走事务并强制ROLLBACK或者把写操作重定向到临时表设置statement_timeoutPostgreSQL 可设置单条语句超时、max_execution_timeMySQL 的 max_execution_time 只对 SELECT 生效等数据库端限制返回行数限制在 SQL 外包一层LIMIT或使用游标分批取数避免一次性拉回千万行。注意只读账号一定要验证过“真的只读”。有的 DBA 给账号授权时把SELECT权限给全了但忘了回收CREATE TEMPORARY TABLE或存储过程权限这类漏洞在 agent 场景同样会成为攻击面。上线前建议专门做一轮权限核验脚本。3.2 超时与熔断别让一条 SQL 拖死整个会话大模型生成慢 SQL 太常见了。模型不知道表的数据分布不知道索引情况生成的 SQL 可能因为 join 顺序不佳跑几分钟。执行层必须设置两级保护单条语句超时数据库端设置statement_timeout应用端再设置一个更短的客户端超时比如数据库端 60s应用端 45s。应用端超时先触发时主动 cancel 数据库会话避免连接池被慢查询占满。Agent 级熔断统计单个会话内 SQL 执行失败的次数或累计耗时超过阈值就终止整个 Agent 链路要求用户重新描述问题。防止“模型反复生成同类错误 SQL”形成死循环。熔断参数要根据业务调BI 查询类系统可放宽到 120s交互式问答建议 15~30s 内返回否则用户体验崩盘。我在生产里见过一个调优技巧先让模型生成时会习惯性带上预估扫描行数如果模型判断扫描行数超过阈值就改写为聚合查询或提示用户加过滤条件从源头降低超时概率。3.3 观察性每一步都要留痕Agent 链路里最痛苦的事是什么是模型生成的 SQL 错了但用户只看到“查询失败”你也看不到是哪一步出了问题。所以在设计时就要内置全链路日志记录用户原始问题、模型生成的 SQL、校验结果、执行状态、耗时、返回行数把 SQL 和 schema 版本绑定记录方便回溯“是不是上游表结构变更导致失败”对成功样本和失败样本做标注沉淀为后续微调或 few-shot 的语料。这一步看起来不“性感”但长期价值极大。我见过很多团队绩效汇报时张口要“效果数据”结果发现连日志都没接只能靠肉眼翻。观察性不是一个功能而是 Text-to-SQL Agent 后续迭代的地基。4. 执行后反馈结果解释、智能采样与错误恢复第三件事是 SQL 执行完之后的动作。很多人觉得“执行成功就结束了”但用户真正需要的是对结果的理解。这节我拆成三块结果转自然语言、结果采样与可视化、错误恢复与多轮修正。4.1 结果解释从行数据到结论SQL 返回的是一张二维表用户是带着自然语言问题来的所以 Agent 必须把表翻译回语言。基础做法是把结果集摘要 原始问题一起交给大模型让它生成一段结论性回答。但这里有个大坑如果结果集很大不能全量塞给大模型。我常用的策略是先对结果做聚合统计比如总行数、前 N 行样例、数值列的 min/max/avg模型基于这些统计量生成回答而不是逐行翻译如果用户需要明细再提示“已返回前 100 行样例可导出完整结果”。这个方案的好处是 token 消耗可控、回答聚焦。比如用户问“哪个月销售额最高”你返回给模型的不应该是几百行月度明细而是“1月 1200万、2月 980万……”这些统计摘要模型自然能给出简洁结论。4.2 结果采样与可视化图表语言比数字更直观Text-to-SQL 的价值不仅是“查出数”还要“看懂数”。执行后可以自动判断结果是否适合可视化如果查询结果只有两三列且是“维度-指标”结构直接建议画柱状图或折线图如果是多维表则建议用透视表。实现上可以把结果按图表 JSON schema 输出让前端直接渲染。我个人经验别让模型自由选择图表类型因为它经常会选个花哨但不适合的图。更好的做法是预设规则比如一个维度 一个指标 → 柱状图或条形图时间维度 指标 → 折线图两个维度 一个指标 → 堆叠柱状图或热力图占比场景 → 饼图但只有不超过 5 个分类时建议用。这些规则可以由数据平台团队预置模型只负责判断图的标题和结论不要让它决定图类型。4.3 错误恢复与多轮修正Agent 的“自我修复”能力SQL 执行失败时Agent 不能直接摆烂说“语句执行错误”。更合理的做法是把异常信息作为反馈让模型自动改写 SQL重试一两次。比如报错是“列不存在”把真实 schema 中的相似列名列表反馈给模型报错是“函数不存在”把该方言支持的函数名列表反馈给模型报错是“超时”提示模型增加过滤条件或改写为更轻量的聚合。但重试必须有次数上限我一般设 2 次并且要记录每一次的失败原因。否则遇到模型死循环API 费用和数据库压力都扛不住。还有一个我踩过的坑不能把原始报错原文直接抛给模型数据库的报错有些会泄露表结构细节也会让模型学到不该学的“绕过技巧”所以要做一次脱敏只提取错误类型和可操作字段。5. 完整落地方案从提示词到模块编排讲完三大模块最后给出一套可参考的落地骨架。这里不贴完整代码但会把模块边界、接口定义、和关键配置讲清楚方便你直接抄作业。5.1 模块编排Agent 主控逻辑我理解中的 Agent 主控逻辑大概是这样一个状态机不用纠结术语重点是流程接收用户问题先做问题理解与改写判断是否涉及时间、地域、指标口径调用 schema 检索模块召回候选表与列拼装上下文大模型生成候选 SQL可生成多条用自洽性选择得分高的进入执行前校验流水线权限校验 → 表结构硬校验 → 语义安全与成本预估若校验失败将失败原因反馈给模型要求改写最多 N 次校验通过后进入执行沙箱设置超时与行数限制执行成功 → 结果解释与可视化建议 → 返回用户执行失败 → 提取脱敏错误 → 反馈模型改写 → 重试全部重试失败 → 返回“无法完成这是失败原因”并给出人工反馈渠道。这个状态机看起来简单但落地时最难的是第 2 步的 schema 检索和第 4 步的校验流水线。这两个模块做扎实了后面就顺了。5.2 关键技术选型参考给一个我实际用下来相对顺手的组合不一定是最优但能少走弯路模块推荐方案说明SQL 解析sqlglot跨方言、AST 解析稳、支持转译Python 生态首选方言支持sqlglot 转译让模型统一生成一种方言再转译到目标库减少模型方言错误权限策略存储自研 中间表用策略表存“用户-角色-资源”关系配合数据权限服务执行沙箱read-only 副本 / 数仓优先独立账号写操作走临时库大模型GPT-4o / Claude / 开源 Qwen 系列生成和改写用同一个模型即可重试提示词里加错误上下文可观测性结构化日志 追踪 ID每条请求生成 trace_id贯穿全部日志注意 sqlglot 的坑它对不同方言的 AST 解析支持程度不一样生成 SQL 时尽量让模型输出标准 SQL 或你选定的一种方言再由 sqlglot 转译。如果让模型直接输出“方言 A 原生语法”sqlglot 解析时偶尔会翻车尤其是 MySQL 的LIMIT写法、SQL Server 的TOP、分页 offset 等地方。5.3 生产环境配置清单最后附一份我在生产环境用的配置清单你可以直接对照检查数据库账号只读、无 DDL 权限、无临时表权限、单用户连接数限制语句超时PostgreSQLstatement_timeout30s应用端客户端超时 25s返回行数默认LIMIT 200明细导出走异步任务重试次数执行错误 2 次、校验错误 2 次总计不超过 4 次日志字段trace_id、用户 ID、原始问题、生成 SQL、校验结果、执行耗时、错误信息、返回行数风险操作名单DROP、TRUNCATE、ALTER、DELETE、UPDATE 全部写入黑名单特殊情况走审批流模型参数temperature 0~0.2生成多条候选时 0.2改写重试时 0避免越改越飘。这里有个值得强调的点DevOps 常把“模型生成的 SQL”直接当作可信输入这是错的。所有在大模型输出和真实执行之间的环节都必须用确定性代码去兜底而不是再用大模型去判断“这个 SQL 安不安全”。大模型可以做语义层面的分析和建议但最终放行与否必须由规则引擎做二元决策。6. 常见问题与排查技巧实录最后分享几个在实战里高频踩坑的问题每条都附了排查思路可以直接当成速查表用。6.1 表名对了列名却全是幻觉怎么办现象模型生成的 SQL 表名完全正确但 WHERE 和 SELECT 里的列名频繁出现不存在的列。排查顺序先看 schema 检索模块是不是只注入了表名、没注入列名再看注入的列名是否有注释模型在缺少注释时更容易瞎编语义字段最后看是否做了硬校验如果只是“建议”而非“强制”模型很可能无视。解决方向列名与注释在 prompt 里加粗排列TOP 列数 30 左右保留硬校验逻辑模型生成的列名即使错了也要能准确报错并反馈真实列名列表。6.2 执行超时但数据库端慢查询日志里没有对应记录这个遇到过好多次。原因一般是应用端先超时取消但数据库连接的 cancel 没生效尤其 MySQL 的KILL QUERY和驱动版本有关导致慢查询其实还在跑。排查时检查应用端超时后是否主动调用了 cancel 接口检查数据库连接池是否复用了一个已取消但实际还在跑的连接统一在应用层做“超时→kill→记录日志”的原子操作不要只依赖数据库端超时。6.3 用户说“给我看数据”Agent 返回了“执行成功”却没下文这是典型的缺“执行后反馈”模块。结果解释不是可选项而是必选项。做法执行成功后强制走“结果摘要 → 模型生成结论”流程哪怕结论是“查询成功无数据”也要给用户一个明确反馈而不是抛出空结果。6.4 同一问题多轮对话下模型容易把上下文搞混比如用户先问“华东区销量”再问“那华南呢”Agent 如果每次都用独立 SQL 生成不携带上文很容易丢条件。最佳实践是维护一个“对话级查询上下文”把上一轮的解析结果、过滤条件、指标口径摘要传给下一轮的 schema 检索和 prompt 构建。但要注意上下文的 token 预算只传结构化摘要不传历史 verbose 日志。6.5 Agent 开发时如何判断是模型问题还是链路问题我的经验法则是把同一个问题用固定 prompt 直接问大模型如果模型能输出正确 SQL说明问题出在链路上下文构造或模块编排如果模型本身输出错误再判断是 prompt 引导不足还是模型能力不足。这样划分能省掉大量 debug 时间。链路问题优先查 schema 检索和上下文拼装模型问题优先查示例要不要加到 few-shot 里。结尾一点个人体会做了这么久 Text-to-SQL 和 Agent 开发我最大的体会是这个方向的难点从来不在“让大模型学会写 SQL”而在于你愿不愿意把系统当作一个生产级工程来做。权限、熔断、观察性、错误恢复都是听起来不酷但真正决定生死的东西。如果你正在做类似的 Agent 项目我建议先不要急着上复杂方案把本文说的“执行前拦截、执行中管控、执行后反馈”三个闭环先补上哪怕每个模块用最朴素的方式实现整体稳定性都会有质的提升。最后再分享一个小技巧上线前准备一套“故意捣乱”的测试集包括越权查询、危险 SQL、超时语句、列名幻觉、空结果用这套用例专门打自己的系统比任何演示数据都管用。
返回列表