[控场AI]
· 10 分钟阅读· 5,023 字

0sql 评测:让 AI Agent 生成 SQL 更确定更安全

0sql 评测:让 AI Agent 生成 SQL 更确定更安全

0sql 用语义层把 JSON 规格编译为受治理 SQL,兼顾确定性与三层安全。

0sql 是一个位于 AI Agent 与数据仓库之间的语义层,主张不让模型直接写裸 SQL,而是让 Agent 调用 `search_fields` 发现字段、构造结构化 JSON 规格,再由引擎编译成受治理的 SQL。其核心优势有三:一是确定性,同一规格永远返回同一 SQL,便于调试、缓存与审计;二是安全性,模型级治理限制可查询字段、参数化输出消除注入、行级过滤器内嵌于模型;三是速度,示例中三字段查询规划仅需约 70 微秒。与 Cube 和 MetricFlow 相比,0sql 面向 Agent 驱动查询,表达能力更强但需维护模型同步。文章也提醒了模型漂移、字段歧义、复杂查询映射困难与托管 API 限流四类失败模式,并给出适用与不适用场景的清晰判断。

0sql 评测:让 AI Agent 生成 SQL 更确定更安全

0sql 语义到 SQL 引擎

当 AI Agent 直接从自然语言生成 SQL 时,你会面临两个问题:查询构造的非确定性,以及数据库的直接暴露。0sql 把自己定位为一个语义层,坐落在 Agent 和数据仓库之间,将业务术语规格翻译为受治理的 SQL 语句。

据 0sql 官方文档和 Hacker News 上的讨论,其核心思路是:与其让 Claude 或 Copilot 直接写裸 SQL,不如让 Agent 调用一个 plan_query 接口,传入类似 {projections: [{field: "Web Net Paid"}]} 的规格。引擎返回一条基于治理模型规划的 SQL 语句。同样的规格,永远返回同样的语句。

纯语义到 SQL 的工作方式

0sql 并不直接解析自然语言。它期望的是结构化输入:一个 JSON 规格,通过语义名称引用字段、过滤器和聚合。Agent 的工作是把用户意图映射到这个规格上,引擎的工作是把规格编译成 SQL。

完整流程如下:

  1. Agent 收到用户查询:“show emea profit last 30 days”
  2. Agent 调用 search_fields 工具查找匹配的度量和维度
  3. Agent 构造规格:{projections: [{field: "Profit"}], filters: [{field: "Region", operator: "=", value: "EMEA"}, {field: "Date", operator: ">=", value: "30d"}]}
  4. Agent 用该规格调用 plan_query
  5. 引擎返回基于语义模型规划好的 SQL

语义模型定义了度量(聚合)、维度(分组键)和连接关系。引擎将规格翻译为遵守这些定义的 SQL 语句。如果模型规定“Profit”是连接到 region.name 的 sum(sales.profit),引擎就会生成相应的 SELECT、JOIN 和 WHERE 子句。

语义模型(Semantic Model) 是 0sql 的核心构件,本质上是一份以 YAML 或 JSON 描述的业务语义契约。它把数据仓库中散落的物理表和列,映射为业务人员熟悉的度量、维度、连接与过滤规则。典型定义包括:某个度量的聚合表达式(如 sum(sales.profit))、某维度的来源列(如 region.name)、表之间的连接键,以及可附加在查询上的行级安全条件。模型通常受版本控制,存储在 Git 仓库中,因此每次 SQL 规划都能追溯到具体的模型版本。它带来的直接收益是把“什么是利润”“利润属于哪个区域”这类业务判断从 Agent 的提示词中抽离出来,固化在可评审、可测试的模型里,避免同一术语在不同对话中被解释成不同 SQL。

三层安全边界

安全模型分为三层,这是 0sql 最值得关注的设计。

模型级治理:语义模型定义了哪些字段存在以及如何聚合。如果某个字段不在模型中,Agent 就无法查询它。这防止了 Agent 访问原始列或构造任意连接。

参数化输出:引擎返回的 SQL 中,用户提供的值以占位符形式存在。Agent 从不把字符串插值进 SQL。这在查询构造层消除了注入风险。

行级安全:模型可以编码行级过滤器。如果用户只能看到自己区域的数据,模型会在每个查询中包含该约束。Agent 不需要知道这些规则的存在。

参数化查询(Parameterized Query) 是指 SQL 语句中的用户输入不直接拼接进字符串,而是以占位符(如 ? 或 $1)出现在语句模板中,实际值在执行阶段由数据库驱动单独传入。传统 SQL 注入之所以成立,是因为攻击者能通过闭合引号并追加 OR 1=1 等片段改变语句结构;参数化后,值始终被当作数据而非代码解析,结构由规划器固定,注入面被消除。0sql 在引擎内部即完成这一转换:Agent 传入的 value: "EMEA" 只会落到绑定参数里,不会出现在返回的 SQL 文本中,因此 Agent 即便被恶意提示诱导,也无法通过值字段改写查询语义。

确定性与缓存带来的工程价值

引擎保证同样的规格产生同样的 SQL。这一点在三个场景中至关重要。

调试:如果 Agent 生成了坏查询,你可以通过重跑规格来复现它,不需要重放整个 LLM 对话。

缓存:你可以按规格哈希缓存 SQL 语句。如果 Agent 问了同样的问题两次,可以跳过规划直接返回缓存的语句。

审计:你可以记录规格而不是 SQL。规格更小、更可读,而且不会泄露 schema 细节。

规划速度很快。示例中三字段查询耗时 70 微秒,足以放在 Agent 循环的热路径上。

与 Cube 和 MetricFlow 的对比

0sql 面对的是 Cube 和 MetricFlow 这两个语义层竞品。关键差异在于定位不同。

特性0sqlCubeMetricFlow
主要用例Agent 驱动查询嵌入式 BI 仪表板dbt 原生指标
查询接口JSON 规格 → SQLGraphQL、REST、SQL带指标引用的 SQL
确定性保证显式(同规格同 SQL)隐式(查询计划缓存)隐式(宏展开)
安全模型模型级 + 行级数据访问策略dbt 暴露 + 授权
Agent 集成CLI + HTTP API 工具调用REST API(需包装)SQL 接口(无工具边界)
表达能力完整 SQL 特性集受 GraphQL schema 限制受 dbt 宏系统限制

从表格可以看出,0sql 的表达能力更强,因为它不约束查询形态。Cube 的 GraphQL schema 限制了你问什么,MetricFlow 的宏系统限制了指标如何组合,而 0sql 可以把任意规格编译为 SQL,只要字段存在于模型中。

语义层(Semantic Layer) 是位于数据仓库与上层应用之间的抽象层,用业务术语而非物理表列来定义查询接口。它的价值有三:一是治理,把指标口径、行列权限集中管理;二是复用,同一指标在 BI、Agent、报表中保持定义一致;三是解耦,物理 schema 变更时只需调整语义模型,上层查询不受影响。Cube 和 MetricFlow 属于较早一代语义层,主要服务嵌入式分析、BI 仪表板或 dbt 指标工作流,查询入口是 GraphQL、REST 或带指标引用的 SQL。0sql 沿用同一理念,但把调用方从人类分析工具换成了 AI Agent,因此接口设计为工具调用友好的 JSON 规格,并显式承诺确定性输出。

CLI 与 Agent 集成模式

0sql 随附一个 CLI(zsql),封装了 HTTP API,专为能执行 shell 命令的编码 Agent 设计:

zsql sql --expr "web net paid, web sold date, net paid"

CLI 把 SQL 返回到 stdout,Agent 可以将其管道传给查询执行器或记录审查。

对于使用函数调用(OpenAI、Anthropic)的 Agent,你可以把 HTTP API 暴露为工具。Agent 调用 search_fields 发现可用字段,再调用 plan_query 生成 SQL。整个过程中,Agent 从不直接写 SQL。

架构形态

引擎是无状态的。它从 Git 分支(存储在 0sql 云中)读取语义模型,按需规划查询。你可以通过把 CLI 指向自己的模型仓库来自托管。

架构链路如下:

Agent (Claude)
  │ 1. search_fields("profit")
  ▼
0sql API (stateless)
  │ 2. 从 Git 加载语义模型
  ▼
模型仓库 (YAML/JSON)
  │ 3. plan_query(spec)
  ▼
SQL 生成器
  │ 4. 返回 SQL
  ▼
Agent (执行 SQL)

语义模型定义四类要素:度量(如 sum(profit)、count(orders))、维度(如 region.name、date)、连接关系、以及行级安全过滤器。引擎只编译这些,不执行查询。Agent 负责把 SQL 跑在数据仓库上。

需要警惕的失败模式

0sql 并非万能,文档明确列出了几种失败模式。

模型漂移:如果语义模型与仓库 schema 脱节,引擎会生成无效 SQL。你需要一条 CI 流水线来验证模型与仓库的一致性。

字段名歧义:如果模型定义了两个相似名称的字段(“Profit”和“Net Profit”),Agent 可能选错。search_fields 工具会返回多个匹配,但最终选择权在 Agent。清晰的命名约定是必需的。

复杂查询:引擎为任意规格生成 SQL,但复杂查询(窗口函数、递归 CTE)可能无法干净地映射到语义模型。你可能需要定义自定义度量或回退到裸 SQL。

限流:HTTP API 由 0sql 托管。如果超出速率限制,查询会失败。你需要重试逻辑或自托管部署。

模型漂移(Model Drift) 指语义模型的定义与实际数据仓库 schema 之间逐渐产生偏差。常见诱因包括:上游表结构变更、列重命名、聚合口径调整、分区字段替换,而语义模型未同步更新。漂移不会在模型加载时报错,而是在查询执行时才暴露为无效 SQL 或错误结果。防御手段包括把模型校验纳入 CI,在每次仓库 schema 变更后自动比对待查字段是否存在、连接键类型是否匹配;同时在生产环境记录规划失败率与 SQL 执行错误,将异常规格回灌到测试用例中。对依赖 0sql 的 Agent 管道而言,模型漂移是最容易被低估却被高频触发的故障源。

技术判断:什么时候该用 0sql

适合使用的场景:

  • 你在构建 Agent 驱动的分析,需要防止 SQL 注入
  • 你想要确定性的查询生成,用于缓存和调试
  • 你需要在语义层强制行级安全
  • 你在集成能调用 HTTP API 或 shell 命令的编码 Agent

应该避免的场景:

  • 你需要完整的 BI 平台,含仪表板和可视化(用 Cube 或 Metabase)
  • 你的查询太复杂,无法在语义模型中表达(用带参数化的裸 SQL)
  • 你需要亚毫秒级查询规划(HTTP 往返增加延迟)
  • 你想自托管但不想管理模型仓库

核心价值是在查询构造层的确定性和安全性。如果 Agent 直接写 SQL,你有注入风险和非可复现的查询。如果 Agent 使用 0sql,你得到的是受治理的 SQL 和清晰的审计轨迹。代价是表达能力:你只能查询语义模型定义的内容。

对于正在构建 Agent 数据管道的团队,0sql 提供了一条介于“让 LLM 裸写 SQL”和“完整 BI 平台”之间的中间路径。它的取舍很清晰,适合那些把安全和可审计放在首位的场景。

参考来源

#0sql#语义到SQL#AI Agent SQL生成#SQL注入防护#确定性查询生成#语义层#Cube对比#MetricFlow对比
分享:

相关推荐