我要提问
ARTICLE DETAIL

资讯详情

前沿编程新知与开发实战干货的深度解读。

不依赖LLM的自然语言转SQL:基于规则引擎的隐私优先方案

不依赖LLM的自然语言转SQL:基于规则引擎的隐私优先方案 我在 Hacker News 上发了一个项目标题是 Privacy-First Natural Language to SQL, No LLMs, No Uploads。熟悉 HN 生态的朋友都知道Show HN 是一个相当硬核的曝光渠道——没有营销包装全球的工程师会直接对你的作品开火。我发这个项目的初衷其实是从内部工具的实际痛点出发的。团队里不少业务同学写 SQL 很吃力日常又频繁需要从数据库里捞数。市面上已有的 NL2SQL 方案几乎清一色绑定大型语言模型要么把数据送到云端推理接口要么至少也要过一遍外部服务。这带来两个非常现实的问题第一敏感业务数据外泄的风险很多公司根本不敢把库里的数据喂给第三方第二LLM 的调用成本和响应速度在频繁交互的场景下并不理想。所以我把整个方案推倒重来不用任何 LLM不发起任何网络请求纯本地规则引擎实现自然语言到 SQL 的转换。数据不出机器逻辑完全可解释响应时间控制在毫秒级。这篇文章就把我整个开发过程中的设计取舍、实现思路和踩过的坑完整梳理一遍。1. 这个项目到底在解决什么问题1.1 现有 NL2SQL 方案的三大痛点先把行业现状摊开说。今天市面上绝大多数的自然语言转 SQL 工具底层都是 LLM。你可以用 GPT、Claude、开源大模型也可以自己微调一个专用模型但无论什么路线本质上都逃不开三个问题成本、延迟、隐私。成本不用多讲按 token 计费的模式在复杂查询场景下很贵。一条上下文较长的查询请求加上系统提示词和数据库 schema 描述随随便便消耗几千 token。如果是团队内部高频使用一个月下来账单相当可观。延迟更直接网络请求加模型推理常规情况下少则一两秒多则十几秒对习惯了在终端里秒出结果的人来说非常难受。隐私是最微妙也最致命的一点。即便你用的是私有化部署的开源模型数据至少还在自己的可控环境里。但很多中小团队为了省事直接调用云端 API这意味着表结构、字段名甚至查询结果都要经过第三方服务器。对金融、医疗、企业内部管理系统来说这种模式基本过不了安全评审。1.2 为什么“隐私优先”能成为切入点我一开始的思路就很明确能不能做一个完全不依赖外部服务的 NL2SQL 工具数据不离开用户机器模型不调用远程推理甚至不需要安装 Python 环境之外的东西。这里有一个重要的认知转变。做这个项目之前我也默认“自然语言理解”必须靠神经网络。但深入想了一下数据库查询这个场景其实极其受限。一个业务数据库不过几十张表、几百个字段用户能发出的查询意图其实非常有限——无非是查什么、筛什么条件、怎么聚合、怎么排序。这套东西的复杂度天花板很低完全没有到必须动用大模型的程度。于是项目定位就出来了面向结构化程度高、访问频繁、对隐私敏感的内部数据查询场景做一个轻量、快速、可解释的本地 NL2SQL 引擎。和 LLM 方案拼的不是语义理解的宽度而是在固定 schema 上的准确率、速度和零数据外泄的确定性。2. 没有 LLM怎么理解自然语言2.1 核心假设数据库查询是一个受限问题很多人听到“不用 LLM 做 NL2SQL”的第一反应是不现实但实际上这个问题的难度被 LLM 时代夸大了。语言模型解决的是开放世界的语义理解问题而数据库查询只需要在封闭的 schema 空间里做映射和组合。我可以把一个查询拆解成几个基本维度对象用户要查哪些表、哪些字段条件对数据有怎样的过滤要求等于、大于、区间、包含等聚合是否需要分组、计数、求和、平均排序与限量按什么排序取前多少条这些维度的组合方式虽然多但每一种都可以用规则覆盖。比如“查一下 3 月份销量超过 100 的产品名称”这句话拆开就是对象产品名称、条件销量 100、日期在 3 月、结果输出查询某个字段。这完全不需要理解“产品”是什么哲学概念只需要把它映射到表里的某个字段。所以我的架构从一开始就往这个方向设计用轻量级自然语言解析器做意图识别不做开放式的语义建模。2.2 规则引擎与轻量意图识别的组合拳实现的时候我放弃了传统的正则表达式暴力匹配——那东西写出来看着热闹维护起来是灾难。我用的是一个更清晰的分层结构词典层维护表名、字段名的同义词映射比如“销售额”映射到total_amount“用户”映射到user表、“名字”映射到name字段。意图层识别查询中的动作类型比如“统计/汇总/每个”指向 group by 和聚合函数“比/大于/超过/在...之后”指向比较条件。结构层把识别出的意图片段按 SQL 语法树组织起来生成最终的 SQL。其中意图分类部分我用了一个非常轻量级的策略关键词与上下文规则的混合。自然语言表达丰富的场景用关键词表覆盖常见的表达方式同义多变的场景靠上下文规则去推导。比如“超过”“大于”“不低于”都指向比较但“不低于”本身带了一个否定前缀需要在规则里单独处理。这套逻辑的好处是完全可控。每一条规则都有据可查生成的每条 SQL 都能解释来源。做不到的事情直接告诉用户“这个查询我无法理解”而不是像 LLM 那样一本正经地生成一条错误的结果。3. 解析管线的完整设计3.1 输入预处理分词、规范化与停用词自然语言进系统的第一道关卡是预处理。英文环境我直接用空格分词加词形还原中文环境需要额外处理分词问题。我用的是双向匹配的最大分词算法配合自定义词典把“销售额”“订单量”这类业务词优先识别避免被拆碎。规范化阶段做几件事统一大小写、去除无效停用词、将中文数字和英文数字表达式统一成阿拉伯数字。这里有一个容易被忽略的细节——日期表达的处理。用户的输入千奇百怪可能是“2024年3月”“去年”“上个月”“最近30天”这些需要分别转换成 SQL 里的日期范围条件。以“最近30天”为例规则引擎会基于当前日期往前推 30 天生成created_at 2024-01-01 AND created_at 2024-01-31这样的区间条件。这里的关键点在于需要知道数据库里那张表的日期字段到底叫什么这就需要 schema 信息参与映射。3.2 Schema 感知的实体映射策略我前面反复强调 schema 是核心资产。实现上项目启动时会读取数据库的系统表获取完整的表名、字段名、字段类型和外键关系构建一棵内存中的 schema 树。实体映射的本质是把自然语言里出现的名词短语映射到 schema 树上的节点。这个映射分三层精确匹配用户输入的表名或字段名与 schema 完全一致这个最简单。同义词匹配通过词典把业务术语映射到底层字段比如“手机号”映射到字段mobile。模糊匹配基于编辑距离或子串匹配处理用户记不清确切字段名的情况比如输入“createtime”时能够命中created_at。第三层最容易出错。编辑距离阈值调太紧匹配不到调太松产生大量误匹配。我最终的策略是优先精确匹配其次同义词最后才用基于 schema 上下文比如字段类型、所属表的模糊匹配兜底并且模糊匹配命中时必须向用户二次确认。这一层的设计投入决定了整个系统体验的上限。很多方案在这块做得很粗糙导致用户必须按数据库字段名“对着字典说话”那自然语言就失去了意义。3.3 条件解析与 SQL 组装的安全护栏条件解析是最容易踩坑的地方。实体映射得到的是“哪些表和字段”条件解析要回答的是“如何过滤”。比较符号、逻辑连接词、括号组合、类型转换每一步都需要验证。我举个例子用户输入“价格不超过 100 且库存大于 0 的商品”。解析后得到条件price 100 AND stock 0。这里面有个隐藏问题——用户说“价格不超过”到底是还是我做了语义收紧处理即“不超过”映射为因为用户的隐含语义是包含 100 这个边界值的。这种细节没有标准答案但在真实业务场景中直接决定了结果对不对。SQL 组装阶段我没有选择拼接字符串的方式而是用一个结构化的查询构建器逐步把 SELECT、FROM、WHERE、GROUP BY、ORDER BY、LIMIT 这些子句组装起来每一层都做合法性校验。字段名来自 schema 树而不是用户输入的原样复制从机制上避免 SQL 注入。用户输入永远只是“值”的载体不参与结构生成。4. 隐私架构与本地部署实现4.1 数据流向一次请求都不往外发隐私优先说起来容易做起来难难在如何让用户真正信服。我在这块做了两个层面的保障。第一是架构层面的强制约束。整个引擎作为一个本地库嵌入到应用中所有的解析、映射、查询构建都在进程内完成代码里没有任何 HTTP 客户端也没有任何外部服务地址。从工程上就杜绝了数据外发的可能性。第二是运行时的透明可审计性。系统会记录每次查询的完整解析链路用户输入了什么词典命中了哪些词条映射到了哪个字段生成了哪条 SQL。这个日志存在本地用户随时可以回看。这种透明度是 LLM 黑盒方案不具备的。另外我单独处理了数据库连接信息的敏感性。配置项支持环境变量注入和本机加密存储不会以明文形式写入配置文件。4.2 规则与词典的分发方式不用 LLM 之后规则引擎的迭代靠的是词典和规则的持续更新。词典文件以 YAML 格式维护发布时打包进应用。每个部署环境可以自定义自己的业务词典。这里有一个重要的设计决策基础规则和业务词典要分层。基础规则负责通用的自然语言理解逻辑这部分由引擎内置业务词典则根据实际数据库 schema 和用户习惯动态配置由部署方自行维护。加载逻辑也很简单——启动时读取 YAML 词典构建 Trie 树提升匹配效率。整个词典的加载和构建时间在几十毫秒以内对启动流程几乎无感知。后续更新词典只需要重新加载不用重启进程这个细节在高频迭代场景下特别实用。5. 和 LLM 方案的横向对比5.1 能力边界与服务差异客观讲非 LLM 方案和 LLM 方案各有清晰的适用边界谁也没办法完全替代谁。我用一张表把实际测试的对比结果放出来供大家选型时参考对比维度本方案规则引擎LLM 方案查询响应时间毫秒级1-10秒单次查询成本几乎为零按 token 计费数据隐私全程本地零外传依赖部署方式云端有风险查询可解释性完整解析链路可追溯黑盒推理复杂语义理解覆盖常见场景复杂问法需规则补齐泛化能力强能理解开放式表达不确定性结果确定同输入必同输出同样的输入可能产生不同结果部署依赖纯本地库无需 GPU/网络通常需要模型环境和算力测试数据来自我内部用一套包含 12 张表、80 多个字段的中型业务 schema 做的实验。规则引擎单次解析平均耗时 18 毫秒LLM 方案平均耗时 2.4 秒差异非常直观。5.2 选型建议什么时候该用哪种方案如果让我给结论我会说固定 schema 的重复性查询场景规则引擎完胜开放式数据分析场景LLM 依然是更合适的选择。细分来看当团队有一个结构相对稳定的业务数据库用户反复查询的是销量、用户量、订单状态、时间区间这些固定维度规则引擎的方案不仅跑得快关键是可维护。业务方提一个需求你加一条词典规则就能搞定不需要重新训练模型也不会出现“同一句话这次查出来和上次不一样”的玄学问题。反过来如果用户面对的是非结构化数据、SSMS 里临时探索性查询、或者需要理解“帮我看看这两个月异常波动的可能原因”这种模糊问题那规则引擎确实无能为力老老实实上 LLM。我的立场不是“规则替代 LLM”而是“给用户多一个选择”。很多内部工具场景用户要的只是跑得稳、查得快、数据安全不关心背后是不是 AI。6. 实操中踩过的坑与排错经验6.1 同义词映射的陷阱第一个大坑是同义词的冲突。同一个业务词在不同语境下可能指向不同的字段。比如“状态”这个词在订单表里可能对应order_status在用户表里对应user_status。如果词典只做全局映射不做上下文约束很容易在 多表 join 的时候映射错。我的解决办法是同义词定义支持作用域限制。词典条目可以指定适用的表范围比如synonyms: - word: 状态 field: status table: [orders, users] # 限定表范围 - word: 状态 field: user_type # 用户表里有时也叫状态 table: users匹配的时候会先锁定候选表再在候选表范围内做同义词映射。这样虽然词典条数增加了但误映射明显减少。6.2 复杂嵌套查询的处理第二个坑是常规规则引擎在嵌套查询面前力不从心。用户问“每个品类下销量最高的前三个产品”这句话里的“每个品类”和“前三个”需要生成一个窗口函数或者在子查询里做排序分组。一开始我的解析器是一个线性流程识别对象、条件、聚合、排序。面对这种嵌套逻辑就直接傻了。后来的改进是把解析流程层级化先识别主查询框架再在主查询的某个组件里递归解析子条件。这种“嵌套解析”模式让系统能够正确处理一层到两层的子查询超过三层的复杂查询依然会提示能力不足。这里我的态度很明确做不到就明说。系统会有意识地识别出自己无法解析的查询模式返回一个友好的提示告诉用户哪些部分没理解而不是生成一条语义错误但语法合法的 SQL。这种“主动认怂”让用户对系统的信任感反而更高。6.3 安全护栏的细节实现安全永远是 NL2SQL 工具的生命线。LLM 方案最大的风险是模型在生成 SQL 时可能产生语义上偏差但语法合法的注入行为。规则引擎因为字段名不来自用户输入结构也不由用户拼装注入风险天然很低但仍然有几个细节要注意。第一个细节是值域的类型校验。用户输入的值会被解析成字符串、数字或布尔值但最终拼进 SQL 时必须按照目标字段的数据类型做一次强制转换。如果用户说“查询价格大于 abc 的产品”系统应该直接报错而不是把abc原样塞进 SQL。第二个细节是 LIKE 条件的转义处理。用户输入里包含%或_时如果不做转义会让查询结果意外扩大。我专门写了一个转义函数确保用户搜什么就匹配什么不会因为通配符产生额外结果。第三个细节是查询行数的兜底限制。自动生成的 SQL 默认加一个 LIMIT 1000 的保护用户确认之后可以调整但默认值永远保守。这避免因为一个宽松的过滤条件把整个线上库拖垮。6.4 调试技巧规则日志就是最好的调试器最后分享一个实操里最有用的经验规则引擎的调试完全依赖日志所以要在一开始就把日志设计好。我在实现里对每个解析阶段都输出中间结构——原始输入、分词结果、命中的词典词条、候选表、优先级排序、最终生成的 SQL。任何一个环节出问题看日志就知道卡在哪里。LLM 方案你只能看到输入和输出中间过程是黑盒没法调。规则引擎的优势就是中间每个环节都在掌控中也正因为如此日志的质量直接决定排查效率。我强烈建议做类似项目的朋友把解析日志做成可读性高的多行文本不要挤成一行 JSON自己看着也省力。结尾做这个项目最大的收获不是“不需要 LLM 也能做 NL2SQL”这个结论本身而是让我重新审视了技术选型里那条默认的捷径。遇到“语言理解”相关的问题我们太习惯直接上大模型了很少停下来问一句这个场景真的需要那么强的理解力吗如果只是一个固定 schema 的数据库几百个字段的组合空间其实有限得很规则引擎完全可以覆盖大部分需求而且跑得更快、更便宜、更让用户安心。当然规则引擎不是银弹它有自己的能力边界。但我始终认为工具的价值不取决于背后算法听起来是否时髦而取决于是否解决了真实场景里的真实问题。从我在 HN 上收到的反馈和实际部署的效果来看这个方向确实戳中了不少人的需求。如果你也有类似内部数据查询的场景不妨试试非 LLM 的路线。哪怕只是把一个高频查询的手工流程自动化了就已经值回开发成本了。
返回列表