资讯详情

资讯详情

Text2SQL智能体实战:从Demo到内部工具的完整方案

上周有个业务同学跑过来问我能不能让AI直接连库查数省得天天排队等数据。我说能但真正要做的不是接一个“自然语言翻译成SQL”的接口而是要搭一个会自己写SQL、自己执行、执行报错了还会自己改的智能体。从那时候开始我把Text2SQL从“demo玩具”一路做到了内部能用的工具这篇就完整复盘一下我的方案选型、架构拆解、核心代码和踩坑记录希望能给正在做或者打算入坑Text2SQL智能体开发的朋友一些参考。这篇文章适合谁一种是研发同学想在公司内部落地一个查数机器人另一种是算法或数据工程师想搞清楚大模型、Agent、RAG这些概念在真实业务里到底怎么组合在一起用。全文没有任何玄学内容都是可以直接抄作业的工程方案。1. Text2SQL智能体到底是什么1.1 为什么Text2SQL一直“叫好不叫座”Text2SQL这个概念提了很多年早在大模型火起来之前就有用模板匹配、语法树映射、规则引擎做的实现。那套东西只对“本月销售额是多少”这种封闭式问题有效一旦问题变成“找出每个品类里销量排名前三的商品但要排除赠品和测试订单”规则基本就崩了。核心原因不是用户不会说话而是自然语言表达业务需求的时候存在大量上下文和隐含信息纯靠规则根本没法覆盖。大模型出现后很多人直接把问题丢给LLM让它生成SQL效果确实比规则时代好了一大截。但单纯依赖“一步生成”依然有巨大问题模型可能编造出根本不存在的表名可能把字段语义搞错可能在执行报错后完全没有修正能力。本质原因是它没有形成闭环——写出来的SQL对不对它自己不知道执行失败的原因是什么它也不会看。这个阶段我见过太多失败的POC最后结论都是“大模型生成SQL不可靠”。实际上不是模型不可靠而是使用姿势错了。Text2SQL的正确打开方式从来都不是“一句话换一段SQL”而是把它变成一个有感知、有行动、能根据反馈自我修正的智能体。1.2 智能体把“翻译SQL”升级成“解决取数问题”智能体Agent和普通prompt工程最大的区别是引入了“循环”模型不再是一次性输出结果而是可以规划多个步骤、调用外部工具、观察工具返回结果再决定下一步动作。放到Text2SQL场景下这个循环大概长这样理解用户业务问题拆解查询意图。查询数据库的元数据表结构、字段注释、索引信息判断哪些表和问题有关。检索知识库补齐业务口径定义比如“GMV按支付成功时间统计”这种规则。生成SQL。在沙箱环境执行SQL。如果报错把错误信息回传给模型让它修改。如果执行成功但结果和预期不符继续反思和重写。最终把结果转成自然语言反馈给用户。这就像带了一个只会SQL的新人分析师他拿到需求后会先查文档、再写代码、再跑任务、报错了会看日志。而传统的Text2SQL更像一个翻译机你说一句它翻一句翻完就走也不管翻得对不对。我自己的经验是一旦把“写SQL”这个单点动作升级成“取数”这个完整任务效果立刻就不一样了。哪怕底层用的还是同一个模型加入工具调用和执行反馈两个环节之后可用性会提高非常多。这也是现在大家愿意投入做Text2SQL智能体而不是直接调一个文本生成接口的根本原因。2. 整体架构与方案选型2.1 一套能落地的系统长什么样我最终落地到内部环境的架构并不复杂核心就四层。第一层是交互层负责接收用户问题。对内可以是Web聊天框对外也可以接企微、钉钉、飞书机器人。这一层的核心工作是做基础的问题改写和意图识别比如把“上周的二狗子渠道”改写成“上周二渠道”这种明显口语化的修正再判断用户到底是想查数、想解释报表还是闲聊。第二层是Agent编排层这是整个系统的中枢。它决定模型先调用哪个工具、什么时候需要检索知识库、SQL生成几次没通过要不要换策略。很多平台型产品把这一层做成了可视化工作流但落到代码层面本质上就是一个基于大模型的循环决策系统。第三层是工具层给Agent提供能操作外部世界的能力。Text2SQL智能体至少要有三个工具元数据查询工具、知识库检索工具、SQL执行工具。可能还需要一个数据预览工具用于让模型确认字段里的枚举值长什么样。第四层是模型层。你可以用商业大模型API也可以部署开源模型。关键是这个模型要支持function calling或tool use能力因为Agent循环是建立在“模型能稳定输出结构化工具调用参数”这个基础上的。整体上这套架构遵循一个原则模型负责决策工具负责执行知识库负责兜底常识。不要让模型凭记忆去猜表结构也不要让模型直接面对裸数据库一切外部资源都通过工具暴露给Agent。2.2 平台型工具 vs 自己写代码智能体开发平台现在非常多Dify、Coze、MaxKB这些我都用过各有特色。平台型工具最大的价值是降低了搭建门槛拖拽节点、配置提示词、绑定数据源基本不需要写太多代码。如果你只是想快速做一个demo或者想让业务团队自己维护流程平台确实是个不错的选择。但做Text2SQL智能体我个人强烈不建议一上来就依赖平台。原因有三个。第一Debug困难。Agent循环里出了问题平台自带的日志往往不够细你很难看到每一轮工具调用的完整输入输出排查问题就像隔着毛玻璃看东西。第二定制受限。当你需要自定义SQL校验规则、做精细的权限控制、接入公司内部的元数据中心时平台的可扩展性往往跟不上。第三数据安全。企业数据库的结构和业务口径是核心资产经过第三方平台转发很多公司安全合规这关根本过不去。我的建议是区分场景如果目的是让业务团队验证“智能体查数”这件事有没有价值用平台快速搭如果目的是真正上线服务几十上百个内部用户一定要自己写编排层。核心逻辑只有几百行代码不值得把命运交给一个你控制不了的黑盒。配套的框架选择上LangChain这类偏重的Agent框架我不是很推荐它的抽象层次太多出了问题反而看不懂。我更倾向于用最轻量的方式自己维护一个Agent循环直接调用模型提供的function calling接口。在工程化的时候再包一层FastAPI服务把对话历史、会话管理、权限校验都写在服务层。2.3 我的选型结论最终我采用的方案是Python FastAPI 一个兼容OpenAI接口的大模型API SQLite/MySQL双数据源适配。没有上百行的高级Agent框架核心Agent循环代码就两百行左右剩下的全是工程化包装。这个选型最大的优点是“能看懂”。每一次模型调用、每一次工具返回值、每一次重试触发我都可以通过结构化日志完整还原。生产环境里可调试性就是生命线出了问题十分钟能定位比所谓的天花板能力重要得多。另外国产模型API基本都兼容OpenAI格式切换成本很低今天用这家不行明天可以随时换那家完全不需要改动Agent核心逻辑。3. 核心模块拆解从元数据到安全执行3.1 Schema感知先让Agent知道库里有什么Text2SQL智能体最容易被忽略但又最关键的一个模块是让模型知道“数据库里到底有什么”。很多人的第一版实现是把所有表结构都塞进system prompt这在表少的时候可行但一旦表数量超过几十张上下文就被撑爆了模型反而会“遗忘”更重要的信息。正确做法是动态召回。当用户提问“各渠道的退货率”时Agent第一步不是生成SQL而是先调用元数据查询工具把包含“渠道”“退货”“订单”这些关键词相关的表结构捞出来。这个召回过程可以走简单关键词匹配更稳的做法是对表名、字段名、字段注释做embedding用向量相似度召回相关表。我用的是后者启动时把information_schema里的表名、字段名、注释全部读取出来拼接成一段文本后向量化存到本地的向量存储里。用户提问时对问题向量化用余弦相似度取TopK表结构再拼进prompt。这样做既控制了上下文长度又不会漏掉关键表。一个很容易踩的坑是很多表的注释写得非常潦草。例如有个字段叫“status”注释是“状态”但业务里它用的枚举值是“1-有效、2-无效、3-待激活”。这种信息光靠注释根本传不到模型脑子里。我后来加了一个数据预览工具允许模型查看某个字段的枚举值分布效果立竿见影。3.2 知识库与RAG业务口径怎么喂给模型网上经常有人问“AI智能体的企业知识库是不是都放在向量数据库里”。答案是不一定。向量数据库只是知识库的一种存储形态适合做语义检索但企业知识库里还有很多结构化内容和规则可能存储在MySQL、ES甚至配置中心里。Text2SQL智能体的知识库核心价值不是“存文档”而是“规范口径”和“翻译行话”。举个例子业务说“新客”但在数据库里可能对应的是“user_first_order_time 大于 当前时间减30天”业务说“GMV”可能在订单表里对应的是“pay_amount 字段且 order_statuspaid”。这些口径如果不提前告诉模型模型就会自己乱猜结果自然不可靠。我的做法是准备一个“口径库”把高频业务术语、对应字段、统计逻辑、相关SQL片段整理成结构化文档每一条就是一个知识条目。用户提问时通过向量检索把相关的5条口径拼进prompt相当于给模型塞了一份“业务词典”。注意知识库检索本身也可以做成Agent的一个工具让模型自己决定是否需要查、什么时候查。不要所有问题都强行注入知识库内容否则又会和Schema信息抢上下文窗口。3.3 SQL执行沙箱只读、限流、权限控制让模型生成的SQL直接在生产库执行听起来就很危险。这块我非常坚持必须做沙箱保护。最基础的一条是智能体使用的数据库账号必须只读没有delete、update、insert权限权限粒度尽量到表级。能做到这点的前提下再在代码层加一道保险。我写了一个SQL校验函数用sqlglot这个库解析模型生成的SQL检查所有语句的type。非SELECT语句直接拒绝多语句直接拒绝。同时强制给SQL包一层外层查询统一加上LIMIT 100防止模型生成“SELECT * FROM 超大表”这种查询把数据库拖死。超时时间也做了限制单条SQL超过10秒直接杀掉。还有一个容易被忽略的点脱敏。有些业务表里存在手机号、身份证、客户姓名等敏感字段。就算数据库账号只读也不能让智能体把这堆数据全查出来。我在元数据层对敏感字段做了标记并且在SQL改写阶段做拦截如果模型生成的SQL涉及敏感字段先转成聚合统计或者直接拒绝。这既保护了数据安全也避免了合规上的一堆麻烦。4. 实战从零搭建一个Text2SQL智能体4.1 环境准备和一个可运行的最小骨架下面进入实战环节。我用一个简化但完整的最小可运行版本帮你把整个架构串起来。技术栈是Python 3.11 OpenAI兼容接口 SQLite。之所以用SQLite是因为它完全本地不需要额外安装数据库服务方便你跑通链路后再换MySQL。先安装依赖pip install openai sqlglot pydantic fastapi uvicorn再准备一个简单的业务库示例比如订单表orders、用户表users、商品表products。建表时把注释写清楚这是后面Schema感知能正常工作的基础。SQLite建表如下CREATE TABLE users ( id INTEGER PRIMARY KEY, name TEXT, first_order_time TEXT ); CREATE TABLE orders ( id INTEGER PRIMARY KEY, user_id INTEGER, product_id INTEGER, amount REAL, order_status TEXT, created_at TEXT ); CREATE TABLE products ( id INTEGER PRIMARY KEY, category TEXT, product_name TEXT, price REAL );4.2 实现Agent主循环接下来是核心Agent循环。我不打算引入重框架直接用function calling格式自己写。这个循环做的事情非常简单让模型看到可用的工具列表如果它调用工具就执行工具并回传结果如果它不调用工具就认为生成完成。循环内部还能处理错误重试。看代码import json from openai import OpenAI client OpenAI(base_url你的模型API地址, api_key你的API Key) SYSTEM_PROMPT 你是一个数据查询助手。你可以查看数据库表结构相关知识并生成SQL查询。 你可以调用工具获取信息工具返回后继续工作。 最终必须用中文给用户解释查询结果。 注意只允许查询不允许修改数据。 def build_tools(): return [ { type: function, function: { name: query_schema, description: 查询数据库表结构信息, parameters: { type: object, properties: { keyword: {type: string, description: 表名或字段名的关键词} }, required: [keyword] } } }, { type: function, function: { name: execute_sql, description: 执行只读SQL查询返回结果集或错误信息, parameters: { type: object, properties: { sql: {type: string, description: 要执行的SQL语句} }, required: [sql] } } } ] def run_agent(question, max_retries3): messages [{role: system, content: SYSTEM_PROMPT}] messages.append({role: user, content: question}) for _ in range(max_retries): resp client.chat.completions.create( model你的模型名, messagesmessages, toolsbuild_tools(), tool_choiceauto ) msg resp.choices[0].message if not msg.tool_calls: return msg.content messages.append(msg) for tool_call in msg.tool_calls: args json.loads(tool_call.function.arguments) if tool_call.function.name query_schema: result query_schema(args.get(keyword, )) elif tool_call.function.name execute_sql: result execute_sql(args.get(sql, )) else: result 未知工具 messages.append({ role: tool, tool_call_id: tool_call.id, content: json.dumps(result, ensure_asciiFalse) }) return 重试次数用尽需要人工介入。这个循环看起来简单其实已经覆盖了Agent最核心的机制模型有决策权可以自主决定查Schema还是直接执行SQL工具执行结果又会反馈给模型形成闭环。query_schema和execute_sql两个函数你按实际数据库去实现。query_schema里面可以用SQLite的sqlite_master表和PRAGMA table_info获取表结构execute_sql里面要加上sqlglot校验、LIMIT限制和超时控制。4.3 接入知识库检索现在给Agent补上“业务口径”能力。这里我用一个非常轻量的方式实现知识库检索预先把口径条目向量化每次用户提问时计算余弦相似度召回TopK内容拼到Prompt里。代码示例如下import numpy as np def embed_text(text): # 这里调用 embedding 模型接口返回一个向量 resp client.embeddings.create( model你的embedding模型名, inputtext ) return resp.data[0].embedding # 知识库示例 knowledge_base [ {id: 1, term: GMV, definition: GMV按支付成功时间统计金额取orders.amount状态为paid}, {id: 2, term: 新客, definition: 首次下单时间在统计周期内即users.first_order_time在周期内}, {id: 3, term: 退货率, definition: 退货订单数/订单总数退货状态为refunded} ] def build_kb_vectors(): for item in knowledge_base: item[vector] embed_text(item[term] : item[definition]) def retrieve_knowledge(question, top_k2): q_vec np.array(embed_text(question)) scored [] for item in knowledge_base: score np.dot(q_vec, np.array(item[vector])) scored.append((score, item)) scored.sort(keylambda x: x[0], reverseTrue) return [item[definition] for _, item in scored[:top_k]] def augment_with_knowledge(question): kbs retrieve_knowledge(question) if not kbs: return question context \n.join([f- {kb} for kb in kbs]) return f业务口径补充\n{context}\n\n用户问题{question}把augment_with_knowledge的输出作为user content传给Agent模型就知道“GMV不等于所有订单金额只统计paid状态的”。这个方案在几百条口径规模下完全够用等规模大了再换真正的向量数据库。4.4 自动纠错机制怎么加Text2SQL智能体和普通Agent最大的差别在于它的工具会给出“硬反馈”——SQL执行的成功或失败是明确信号不需要模型猜。自动纠错机制就是利用这个硬反馈让模型根据错误信息重新生成。我设计了一套降级策略执行SQL返回错误时做以下几件事。第一把原始错误信息完整回传给模型让它尝试修复第二如果同一个SQL连续报错两次就把真实表结构CREATE TABLE语句再插入到上下文中减少模型猜测字段的可能性第三如果重试3次还是失败启用兜底策略告诉用户问题太复杂建议人工取数。整个流程不需要额外训练模型靠的是Prompt里的明确指令。有一点值得注意有些大模型在上下文里看到报错信息后会自动“脑补”一个正确的SQL但依然不保证能执行。你需要让模型明确知道“禁止猜测看到错误后必须重新查表结构”。我在SYSTEM_PROMPT里特别强调当SQL执行失败时一般是由于字段或表名错误请先调用query_schema确认字段再重新生成。这样做了之后我统计过大概30%的初始SQL会执行出错但经过两轮自纠错后最终执行成功率能到90%以上。这说明纠错机制不是锦上添花而是整个系统真正能用的关键。4.5 没有Agent的基线 vs Agent效果为了验证这套架构不是自我感动我做了一组对照实验。用同样的业务库、同样的业务问题集分别跑两个方案基线方案是把表结构一次性塞进Prompt让模型直接生成SQL并人工判断正确性Agent方案就是上面这套带工具调用和自纠错的循环。结果非常明显。基线方案的SQL语法正确率大约65%但执行成功率只有45%左右因为很多SQL看似合理实际上表名或字段名根本不存在而Agent方案经过动态Schema召回、工具执行反馈、自纠错之后执行成功率能达到90%以上。在业务问题从简单聚合到多表关联都有覆盖的情况下这个提升幅度足够说明问题了。当然这个结果有一个前提表结构注释质量和知识库口径覆盖度不能太差。如果数据库里所有字段都叫a、b、c没有任何业务注释神仙来了也做不好。所以我会劝所有想上Text2SQL智能体的人先花两周把元数据治理好这个投入比调模型prompt值一百倍。5. 实测中的问题与排查手册5.1 它老是把表名、字段名写错这是Text2SQL智能体上线初期最让人崩溃的问题。表名倒是好解决因为query_schema工具会把真实表名带出来模型就算一开始瞎编工具调用后也能纠正。真正难的是字段名尤其是一张表几十个字段、注释还不清不楚的时候模型经常把“product_id”写成“productId”或“goods_id”。排查思路是看日志里的工具调用记录重点确认两点一是query_schema是否召回了正确的表如果关键词不匹配召回结果为空模型就只能瞎猜二是模型是否真的使用了工具返回的真实字段名有些模型为了“省事”会忽略工具结果直接生成SQL。解决的土办法非常有效给query_schema工具的结果里附加一个“该表中字段的枚举值分布预览”让模型对字段内容有直观感知。另外把筛选条件里的高频枚举值同步到字段注释里比如原注释是“渠道”改成“渠道1-线上2-线下3-合作”模型判错率会直线下降。5.2 上下文越聊越长模型越来越傻多轮对话模式下用户会不断追加条件比如“上个月销量”“还是这个但只看华东区”“把时间改成最近7天”。如果每次都把完整历史塞给模型几轮下来token很快就爆炸了而且模型会被历史里的无效信息干扰反而忘记最新需求。我采用的方案是“摘要最新一轮”的结构。每一轮对话结束后单独用模型生成一条简洁的会话摘要记录已确认的查询条件、已确定的口径、当前状态下一轮提问时只把摘要和当前问题交给模型而不是一堆碎碎念的聊天记录。字段级历史查询SQL结果也不保留只保留“最近一次执行的SQL是否正确”这个状态。这个优化对成本影响也很大。我算过一笔账一个业务咨询会话动辄来回十几次如果不做压缩一个会话消耗6万token很常见做了摘要压缩之后稳定在1.5万token以内模型响应速度也快了。5.3 权限边界在真实业务里怎么卡权限这块我踩过一个很大的坑最开始只做了数据库只读账号以为万事大吉。结果业务方跑来投诉说智能体把某个内部渠道的成本数据查出去了虽然没造成事故但流程上完全不合规。后来我把权限做成了“账号级隔离”。整个系统对接多个业务库不同部门走不同代理账号每个账号只能看到自己库的表。同时按照数据敏感级别分了三档公开数据放开查询敏感数据要求做聚合不能查明细高度机密数据直接禁止。这层逻辑写在SQL校验器里Agent生成SQL后必须过一遍敏感度检查才能执行。在技术实现上给元数据表加一个“敏感级别”字段在execute_sql工具内部增加检查规则不通过就返回“无权查询”。这里要特别注意不要让模型自己去理解权限规则它一定会在某些边界情况下犯迷糊。权限判定应该永远是代码做模型只负责生成SQL。5.4 评测数据集怎么设计量化才靠谱很多团队做Text2SQL智能体上线前只靠拍脑袋测试“你觉得好不好用”。这么做在前期还行但要做长期迭代没有量化指标根本没法对比不同模型的差异也没法判断一次Prompt改动是变好还是变坏。我设计评测数据集时按四个难度配比来构造简单查询、多表关联、聚合窗口函数、对抗问题。数量上我建议至少100条太少区分度不足。每条评测样本包含业务问题、预期SQL、预期结果要点、涉及的知识库口径、涉及的权限边界。评测指标我用两个执行准确率EX生成的SQL在测试库执行结果集与标注一致的比例和语义等价率VS用AST比较SQL逻辑结构是否等价适合结果集顺序敏感场景。两个指标结合起来基本能反映SQL生成质量。对抗测试集一定要包含“查询薪资”“删除某条数据”这类危险问题专门检验Agent的防御能力。有了这套评测集每次换模型、改Prompt、调Schema召回策略后跑一遍完整集合并记录指标效果好不好一目了然不用再靠人情味十足的“我觉得还行”。一点个人体会从最初做一个“能出SQL”的demo到后来真正跑在内部供几十个人使用我最深的感受是Text2SQL智能体这件事60%的功夫在线下也就是元数据治理、口径梳理、权限设计30%的功夫在Agent编排让模型用好工具、会自纠错真正花在“调模型”上的时间其实不到10%。很多团队把精力全压在那10%上反而忽略了前90%的基础工程。如果你也想做类似的系统我的建议非常直接先把数据库的注释写好把业务口径文档化然后写一个两百行的Agent循环跑通它再考虑优化。这个顺序反过来大概率会重走一遍我踩过的弯路。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →