一、先看一个会亏钱的错误
有两张表:
orders: order_id=1001, amount=100
order_items: order_id=1001, item_id=A / B / C (3 行)问 Agent:“上个月订单总额是多少?”
Agent 看到 catalog,发现两张表可以 join,于是写下:
SELECT SUM(o.amount)
FROM orders o
JOIN order_items i ON o.order_id = i.order_id
返回 300。
这条 SQL 语法完全合法,执行没有报错,Agent 还会用一段自信的话把它解释一遍。但它错了三倍——join 把订单行放大成了明细行,amount 被重复计了 3 次。这是维度建模里的扇形陷阱(fan trap)。
关键在于:元数据里没有任何一处能阻止这件事。表注释写了”amount 是订单金额”,字段类型是 decimal,血缘图画得很漂亮,但 orders → order_items 是一对多这件事,从来没有以机器可读、可强制执行的形式被声明过。
人类分析师不会犯这个错,因为他知道。Agent 会犯,因为没人告诉过它,而且它没有”我不确定”这个默认动作。
这不是模型能力问题,是架构问题。
二、模型变强能解决吗:解决了一半,剩下那一半更危险
这里有一条难得的纵向证据,同一套题目跨越两年半被测了两次。
2023:16.7% → 54.2%
2023 年 11 月,data.world 的 Juan Sequeda 团队发布了一个企业级基准。数据集是保险领域(schema 基于 OMG 财产与意外险数据模型,后来被称作 ACME Insurance),题目按”问题复杂度 × schema 复杂度”分成四个象限。
结论:GPT-4 零样本直接对 SQL 数据库提问,准确率 16.7%;把同一份数据换成知识图谱表示(Text-to-SPARQL),准确率 54.2%。
Sequeda, Allemang, Jacob, A Benchmark to Understand the Role of Knowledge Graphs on LLM’s Accuracy for Question Answering on Enterprise SQL Databases, 2023-11-13
2024:72.55%,但真正重要的是那 8%
半年后同一团队做了追加实验:在知识图谱路径上加一道基于本体的查询校验(OBQC)——用本体里声明的定义域、值域约束,检查 LLM 生成的 SPARQL 是否走错了路径或搞反了方向;查出问题就把错误解释喂回 LLM 让它重写,最多三轮。
准确率升到 72.55%。但这篇论文里最值得注意的是另外两个数字:
- 8% 的查询在三轮修复后仍不合规,系统返回 “unknown”,而不是返回一个结果。
整体错误率因此从纯 SQL 的 83.3%、知识图谱的 45.8%,降到 19.44%。
还有一个容易被忽略的细节:OBQC 本身不调用 LLM,是纯规则的确定性检查。
Allemang & Sequeda, Increasing the LLM Accuracy for Question Answering: Ontologies to the Rescue!, 2024-05。论文原话是”’我不知道’是一个有效答案,而且可以说比一个不准确的答案更好”。
这条证据的分量在于:真正把错误率砍掉一半的,不是更强的模型,是一层能声明约束、并据此拒绝的结构。
2026:模型确实变强了,然后呢
两年半后,dbt Labs 用同一个数据集、同样 11 道题重跑了一遍(2026 年 4 月),每题跑 20 次。
好消息——全量题目上,text-to-SQL 从 2023 年 GPT-4 的 32.7% 涨到 2026 年的 64.5%,几乎翻倍。
坏消息——64.5% 意味着每三个问题里就有一个答案是错的,而且它长得跟对的一模一样。
而在语义层覆盖范围内的题目上,Sonnet 4.6 和 GPT-5.3 Codex 都是 100%(2023 年是 83.1%)。
补 3 个 dbt 模型、让语义层覆盖全部 11 题之后:
Ganz & Perigaud, Semantic Layer vs. Text-to-SQL: 2026 Benchmark Update, dbt Labs, 2026-04-07。基准代码开源:dbt-labs/dbt-llm-sl-bench
最该盯着看的,是这组对照
dbt 在正式跑基准前先做了一轮交叉测试:4 个模型(Opus 4.6、Sonnet 4.6、GPT-5.3 Codex、GPT-5.2)× 多档推理强度,从 Anthropic 的 low 到 max、OpenAI 的 none 到 xhigh。
结果:语义层路径上,多数组合是 100%,最低的 Opus 4.6 也有 87%;同样这些组合走 text-to-SQL,成绩在 50.0%–65.0% 之间来回晃。 GPT-5.3 Codex 在推理强度 none 档,语义层就是 100%,text-to-SQL 是 50%。
而且推理预算不是没有代价:GPT 系列开到 xhigh 时单次查询平均超过 20 秒(high 档是 8 秒),准确率没有任何提升。
这条对照的含义很直接:
把预算花在推理 token 上,回报接近于零;花在语义建模上,回报是三四十个百分点。
决定答案对不对的,是模型外面那层结构,不是模型本身。
三、所以,语义层到底是什么
一句话:
语义层是把业务概念映射到物理执行的、机器可读的可执行契约,并且它定义了一个封闭的合法查询空间。
这句定义里有三个承重词,逐个拆。
1)“映射”——它在两套语言之间做翻译
业务侧说的是”营收”“活跃客户”“履约率”;物理侧存的是 dwd_ord_dtl_di.pay_amt。这个映射本身就是企业最有价值的资产之一,但在传统架构里它没有存放的地方——它散落在分析师的脑子里、BI 工具各自的数据集里、报表 SQL 的硬编码里。
于是同一个”上月营收”,财务口径按 paid_at 算、运营口径按 created_at 算、都排除了退款单但一个排除了内部测试账号另一个没有——三个数,三个都”对”。
语义层不创造这个映射,它只是第一次把这个映射物化成了独立的、单一的制品。
2)“机器可读的可执行契约”——这是与数据字典的分界线
Confluence 上的指标口径文档也在描述业务概念,为什么它不算语义层?
因为它只能被阅读,不能被执行。它约束不了任何一条真实跑出去的 SQL。写文档的人和写 SQL 的人一旦不是同一个人,文档就开始腐烂。
可执行意味着:Agent 提交的不是 SQL,是意图。
输入: metric = revenue
dimensions = [region, month]
filter = month in last_1
输出: 由引擎编译出的 SQLdbt 用 MetricFlow 做这件事,Cube 用它自己的编译器,Wren AI 用 MDL。名字不同,机制一致:LLM 的职责被压缩到”选对指标和维度”,SQL 由确定性引擎生成。
回到开头那个 300——在这个架构里它压根不可能发生。orders → order_items 的一对多关系是在语义模型里声明过的,编译器知道该先聚合再 join,Agent 没有机会写出那条错的 SQL。不是它变聪明了,是那条路被物理封死了。
这也解释了为什么推理强度在语义层路径上不起作用:当错误路径已经不存在时,多想一会儿不产生任何收益。
3)“封闭的合法查询空间”——最反直觉,也最重要
前两点很多人认同,第三点常被当成缺点。
原始 catalog 是开放世界:N 张表 × M 个字段可以组合出天文数字级的 SQL,其中绝大多数语法合法而语义无意义。LLM 的默认行为是”总要给个答案”,于是这个巨大的合法空间就成了幻觉的温床。
语义层做的事,是把这个空间收缩到有限且已声明:只有建模过的实体、维度、度量和它们的合法组合可以被查询。超出范围?报错,不是猜。
这正是前面那 8% 的 unknown 在做的事,也是 dbt 那篇基准的核心结论——用大意转述:text-to-SQL 的失败长成一个看似合理的错误答案,语义层的失败长成一条报错信息。
对董事会材料、审计底稿、公司级 KPI 来说,这个差别比准确率数字本身更要命。64.5% 的准确率如果失败时会报错,是可以接受的——至少知道哪 35.5% 需要人接手。64.5% 的准确率如果失败时给出一个漂亮的错数,实际可用性接近于零,因为没人能区分。
一个判定测试:给系统一个语义上无意义但语法合法的问题,看它返回错误还是返回一个数。能拒绝的是语义层,只能回答的是文档。
四、代价必须写出来
只讲好处的技术结论不值得信。语义层的代价是真实的,而且同一份基准数据里就有反证。
代价一:覆盖率。 那 11 道题里有 3 道需要过多的实体跳转,MetricFlow 表达不了。在这 3 道题上,语义层准确率是 0%,而 GPT-5.3 Codex 用 text-to-SQL 反而 100% 答对。这是封闭世界假设的直接成本:没建模的问题,一个都答不了。
代价二:建模成本。 上面那个缺口是靠新增 3 个 dbt 模型补上的(这 3 个模型本身由 LLM 生成,人没写代码,但建模决策仍然要人来定)。语义模型的生产是持续的工作量,不是一次性投入。
代价三:一个必须点破的方法论保留。 dbt 在文中自己说明,为了让 text-to-SQL 那一侧跑起来,他们把整个 schema 都塞进了上下文,并指出这在更大的数据集上并不现实。换句话说,65%/90% 这些 text-to-SQL 数字是在一个对它相当友好的条件下取得的。真实企业环境里上万张表,检索本身就是新的失败源,实际表现只会更低。
所以结论不是”用语义层,别用 text-to-SQL”,而是:
口径要一致、数字要进决策的场景(KPI、财报、审计、周报)→ 走语义层,接受覆盖率约束 探索性、一次性、口径尚未沉淀的问题 → text-to-SQL 兜底,但要明确标记”此答案未经验证” 两条路都受益于更好的物理建模——补了那 3 个模型之后,text-to-SQL 也从 64.5% 涨到了 84%–90%
五、给数据团队的一个判断标准
Agent 没有制造出新需求,它只是撤掉了兜底的人。
过去没有语义层也能活,是因为分析师的隐性知识在中间做了缓冲——他知道哪张表能 join、哪个口径要排除测试账号、哪个指标不能跨时间求和。这层缓冲一旦被拿掉,所有从没被显式声明过的东西,都会直接以错误答案的形式暴露出来。
而且暴露方式很不友好:它不报错,只是给出一个错的数,附带一段流畅的解释。
判断标准可以很简单:
如果现在有人问你的数据系统一个语义上无意义的问题,它会返回错误,还是会返回一个数?
如果答案是”返回一个数”,那么这套架构还没准备好让 Agent 直连。
数据出处
本文引用的三组数据均为公开可复现:
三组实验共用同一套 ACME Insurance 题目,因此可以纵向对比。
夜雨聆风