跳到主要内容

SQL Agent 为何在生产环境中失败:针对实时关系型数据库的 LLM Grounding

· 阅读需 13 分钟
Tian Pan
Software Engineer

Spider 基准测试看起来很棒。GPT-4 在数百个测试查询中的 text-to-SQL 转换得分超过 85%。团队看到这些数字,配置好 LangChain 的 SQLDatabaseChain,然后上线“询问你的数据”功能。两周后,一位分析师关于按地区划分收入的无心提问触发了全表扫描,导致报表系统宕机 30 分钟。

基准测试数字是真实的。问题在于,基准测试不使用你的模式 (schema)。

Spider 1.0 在包含 5–30 个表和 50–100 个列的数据库上测试模型。而你的生产数据仓库有 200 个表、700 多个列,根据你查询的系统有三种 SQL 方言,以及在四年前编写代码的工程师看来有意义,但对其他任何人来说都毫无意义的列名。当研究人员推出 Spider 2.0——一个具有企业级规模 schema 和现实复杂性的基准测试时——GPT-4o 的成功率从 86.6% 下降到 10.1%。这种断崖式下跌才是生产环境的真实写照。

这篇文章讨论的是安全部署 SQL Agent 所需的工程设计——不是作为一个模型调用的包装器,而是一个具有 schema 基础化 (grounding)、运行时验证和分层权限的系统。如果其中任何一个环节出错,你要么会得到极其自信的错误答案,要么更糟,在实时数据库上运行了非预期的查询。

SQL 访问不是文档 RAG

当你构建文档 RAG 系统时,近似答案是可以接受的。检索缺失会返回一段相关性稍低的文章;模型用合理的推理填补空白。用户得到一个足够接近的响应,最糟糕的失败模式也只是一个模糊的答案。

SQL 则完全不同。查询要么映射到正确的表名和列名,要么无法执行——或者更糟,它针对错误的数据成功执行。一个混淆了 user_eventsuser_sessions,或者使用 order_date 而不是 created_at 的 Agent,不会返回一个略显不精确的答案。它会返回一个充满自信但完全错误的任务。分析师信任它。决策据此做出。

根本区别在于确定性。文档 RAG 在检索时容忍语义近似。SQL 生成则需要精确的 schema 映射——表名、列名、连接条件、过滤语义——而且没有“同情分”。一个“几乎”能写对 SQL 查询的 LLM,生成的查询看起来很合理,解析也正确,但会静默地返回错误结果。

交付过 text-to-SQL 系统的生产团队一致报告了这一差距。Pinterest 发现,生产环境中的初次尝试接受率约为 20%,而基准测试数字则显示性能要高得多。这些查询并非无法执行——它们执行了并返回了结果,然后被用户标记为错误。

Agent 如何产生 Schema 幻觉

通常的假设是将数据库 schema 注入模型的上下文就能解决基础化 (grounding) 问题。事实并非如此。

当一个 schema 在 200 个表中包含 700 个列时,模型必须在编写一行 SQL 之前,识别出哪些 5–10 个列与给定查询相关,构建正确的连接 (join),并应用正确的过滤条件。即使完整的 schema 都在上下文中,在企业级规模的基准测试中,Schema 链接(将自然语言短语映射到特定的表/列名)仍占 SQL 错误的约 27%。

一旦你看过这些失败模式,它们就是可以预见的:

命名规范漂移。acct_revenue_usd_net_30d 这样的列名内部是一致的,但对于创建它们的团队之外的人来说是不透明的。模型会将“上个月的净收入”映射到听起来合理但可能存在也可能不存在的替代方案。当它们不存在时,你会得到一个错误。当它们存在但含义略有不同时,你会得到一个错误的数字。

组合推理失败。 涉及多个连接、子查询或聚合的查询,要求模型在处理日益复杂的 SQL 结构时,保持原始问题的语义意图。在处理单表查询时具有高准确率的模型,在被迫正确组合多个子句时,准确率会大幅下降。

跨表幻觉。 模型知道某个概念存在于 schema 的某个地方,但不确定哪个表拥有它。它会选择听起来最接近的表。有一半的时间,那个表是存在的。另一半时间,它是来自不同但相关系统的一个表,恰好共享命名模式。

解决方案不是换一个更大的模型。企业级 schema 上的性能崩溃源于 schema 的复杂性和业务上下文的缺失,而不是模型的底层推理能力。在生产环境中达到 90% 以上准确率的团队,是通过 schema 管理而非模型选择来实现的。

Schema 子集化:给 Agent 更少的信息

解决 schema 幻觉的一个反直觉的方法是向模型展示更少的 schema,而不是更多。

这种方法被称为 Schema 子集化 (schema subsetting),它包含两个部分。首先,构建一个检索层,在生成任何 SQL 之前识别哪些表与给定查询相关。这利用了表摘要、列描述和示例查询的嵌入 (embeddings) 来检索候选表集——类似于文档 RAG 检索相关片段的方式,但操作对象是 schema 元数据而非文档块。

Pinterest 的实现具有启发性。他们发现,嵌入表文档(对表内容的平实语言描述)比嵌入原始 DDL 更有效。文档提供了将自然语言问题链接到表的语义信号;DDL 则为 SQL 构建提供了结构细节。他们在检索中给予文档更高的权重,在妥善投入元数据质量后,看到搜索命中率从 40% 提高到 90%。

其次,一旦识别出相关表,在将 schema 注入模型上下文之前,将其精简为仅包含这些表及其相关列。关于客户流失的查询不需要看到支付 schema。关于订单履行时间的查询不需要用户身份验证表。这不仅仅是 token 优化——它减少了模型必须推理的噪声,并降低了幻觉的可能性,因为上下文中的错误答案变少了。

子集化方法需要对 schema 文档进行投资。如果你的表没有描述,列名又像 flg_prm_a 这样,那么检索就是在瞎猜。元数据层是承重的基础设施,而不是可有可无的东西。

执行前的运行时验证

即使进行了模式子集化(schema subsetting),代理(agents)仍会生成不应执行的查询。运行时验证层可以在这些查询到达数据库之前将其拦截。

验证链遵循特定的顺序:

加载中…
References:Let's stay in touch and Follow me for more thoughts and updates