自信地犯错:AI分析师在Snowflake上有无语义模型对比
DataHot 速览
文章用一个可复现实验,让运行在Claude Sonnet 4.5上的AI agent扮演分析师,通过JDBC访问Snowflake的TPC-H演示库(LINEITEM 6000万行、SALES_ORDER 1500万行、CUSTOMER 150万行,X-Small仓库)。在“前”场景中,agent只有JDBC连接和数据模型;在“后”场景中,它获得Snowflake语义视图。没有语义模型时,agent六道题只答对两题,准确率33%。作者指出,错误答案的共性更能揭示企业AI数据策略的脆弱点,并强调语义层对AI分析准确性的重要性。
为什么值得关注:它用具体实验量化了语义模型对AI分析师准确率的影响,并分析错误答案的共性,对评估Data Agent、Text-to-SQL和语义层落地很有参考价值。
译文
AI 逐段翻译
专家们一直在争论,AGI 奇点究竟是已经到来,还是即将来临。不过有一件事大家达成了共识:在没有上下文的情况下把 AI 指向你的数据,它会犯下连你表现最差的实习生都会觉得丢脸的错误。
在最近一篇关于数据建模成熟度阶梯的文章中,我为处于最底层的组织提供了一份自我评估:让 AI 查询你的数据,它不会返回答案,而是会耗费 token 去查询 information schema,然后带着一个看似合理却错误的回答回来。
但“看似合理却错误”是什么样子呢?
为了弄清这一点,我设置了一个 AI 代理来充当分析师,并让它访问我的 Snowflake 数据库。在“之前”的场景中,该代理只有一个 JDBC 连接和一个数据模型;在“之后”的场景中,它配备了一个语义视图。
剧透预警:没有语义模型时,该代理六道题只答对了两道,也就是 33%。但这并不令人意外。
真正的洞见在于那些错误答案的共同点,以及这揭示了贵组织 AI 战略中的什么脆弱之处。
设置
(顺便说一句,如果你不想深究细节,可以直接跳到结果部分:“错误答案的共同点”。)
该数据库是来自 SNOWFLAKE_SAMPLE_DATA 的一个基于 TPC-H 的演示:一个名为 OPERATIONS 的 schema 中有七张表,带有业务友好的名称、声明的键、表注释以及一些列注释——以现实世界的标准来看,这已经相当慷慨了。
LINEITEM 有 6000 万行,SALES_ORDER 有 1500 万行,CUSTOMER 有 150 万行。所有操作都在一台 X-Small 仓库上运行。
对于“之前”的场景,我写了一个小型 Java 测试框架,通过普通 JDBC 连接,让一个代理扮演分析师。该代理运行在 Claude Sonnet 4.5 上,并调用 Snowflake 的 AI_COMPLETE 函数,因此每个 token 都会出现在账户使用视图中。系统提示只告诉它:它已连接到数据库 DEMO_TPCH,它每轮只能运行一条 SQL 语句并最多看到 30 行返回结果,并且它应在有信心时作答。除了数据模型之外没有任何文档。
对于“之后”的场景,我在同样的七张表上构建了一个 Snowflake 语义视图,该代理通过 Cortex Analyst REST API 提出了同样的六个问题。
然后是这六个问题,措辞方式与业务用户提问的方式一致:
- 我们 1995 年的净收入是多少?
- 我们 1996 年关键账户的总订单价值是多少?
- 按总订单价值计算,我们在欧洲的前 5 大客户是谁?
- 我们 1996 年按发货方式的准时交付率是多少?
- 按市场细分的平均订单价值是多少?
- 我们在 BUILDING 细分市场中有多少活跃客户?
其中两个问题可以仅凭结构加一般常识来回答。另外四个问题则需要一条只存在于这家公司内部的规则。剧透预警:猜猜哪些失败了。
没有语义模型时
所有成功的分析都是相似的。每一个不成功的分析则各有各的失败方式,所以让我们按顺序来看。
净收入需要十次模型调用和九次查询。该代理对两张表进行了抽样,检查了日期范围,并给出了这个结果:
SELECT SUM(EXTENDED_PRICE_USD * (1 - DISCOUNT_PERCENT / 100)) AS net_revenue
FROM LINEITEM l
JOIN SALES_ORDER s ON l.SALES_ORDER_ID = s.SALES_ORDER_ID
WHERE YEAR(s.ORDER_DATE) = 1995“1995 年的净收入为 $87,422,985,201.12(按扩展价格减去折扣计算)。如果你还想计入税费,则扣除折扣和税费后的净收入为 $87,388,009,631.52。”
正确答案是 2616 亿美元。毛收入为 3482 亿美元,折扣减少了 174 亿美元,退货又减少了另外 693 亿美元。该代理完全漏掉了退货,因为没有任何信息告诉它 RETURN_FLAG = ‘R’ 表示退货,或者退货要从毛收入中扣除。它把折扣比例除以了 100,因为列名中写着“percent”。
关键账户。该代理花了十二次调用和 17,429 个 token 来寻找定义。它列出了市场细分。它在客户 COMMENT 列中搜索“key”一词。什么都没找到,于是它决定:
“1996 年关键账户(BUILDING 市场细分)的总订单价值为 $69,054,767,591.68,涵盖 457,249 笔订单。在 TPC-H 基准 schema 中,BUILDING 市场细分通常代表关键/主要账户。”
该代理需要一个定义,于是它自己编造了一个,并用权威的口吻作答,好让它站得住脚。真正的答案是 1569 亿美元,涵盖 1,037,899 笔订单,来自 394,360 个按余额定义的关键账户。
准时交付。九次调用。该代理找到了 COMMIT_DATE 和 RECEIPT_DATE,对它们进行了比较,并报告了按发货方式划分的准时率介于 36.69% 到 36.86% 之间。完全合理,但错了两处:它没有应用宽限期,而且在交付绩效按收货日期报告时却按发货日期进行了筛选。真实的比率是 39.16% 到 39.30%。误差幅度也许不算大,但足以侵蚀那些了解内情者的信任。
活跃客户。该代理运行了 COUNT(*) FROM CUSTOMER WHERE MARKET_SEGMENT = 'BUILDING',并报告有 300,276 个活跃客户。然后,它列出了各列,列出了各细分,并在提交前再次运行了完全相同的计数。schema 中没有任何信息表明负的 ACCOUNT_BALANCE_USD 意味着信用冻结,因此 27,201 个当前无法下单的客户被计为活跃客户。
现在说那两道答对的题。
平均订单价值按细分计算用了八次调用,且是正确的。
在欧洲这个问题上,该代理从未查看过任何一个国家名称。它从训练数据中识别出了 TPC-H 的区域编号,将这五个区域名称硬编码进一个 UNION ALL,筛选 REGION_ID = 3,并正确地排出了客户排名。九次调用,43 秒,但答案正确。
注意其中的相似之处:区域映射同样也是部落知识,但它是公开的部落知识,LLM 在训练中已经学过。
在所有六个问题中,该代理共发出了 51 条 SQL 语句,其中 28 条针对 INFORMATION_SCHEMA 或 SHOW 命令,而这些问题之间的语句几乎完全相同,因为没有任何内容被延续下来。而且提示词随着每一轮不断增长。
有了语义模型
语义视图不过100多行代码,描述了七张逻辑表。语义模型承载了那些本来只存在于人们头脑中的业务规则,比如:
- 净收入 = 扩展价格减去折扣减去退货行,不含税,在发货日期确认。
- 关键客户 = 账户余额为5,000美元或以上的客户。
- 信用冻结 = 负余额,且这些客户处于非活跃状态。
- 准时 = 在承诺日期加上三天宽限期之内或之前收到。交付绩效按收货日期报告。
- 欧洲 = REGION_ID 3。没有区域表;这个映射存在于人们的头脑中。
以下是它们在视图中的定义:
facts (
lineitem.net_revenue_amount as iff(return_flag = 'R', 0, extended_price_usd * (1 - discount_percent))
comment = 'Line contribution to net revenue: after discount, before tax, zero for returned lines. Net revenue = gross revenue - discounts - returns.',
lineitem.is_on_time as iff(receipt_date <= dateadd(day, 3, commit_date), 1, 0)
comment = '1 when received within the delivery SLA: committed date plus a 3-day grace period',
...
)
dimensions (
customer.customer_tier as case when account_balance_usd < 0 then 'CREDIT HOLD'
when account_balance_usd >= 5000 then 'KEY ACCOUNT'
else 'STANDARD' end
with synonyms = ('tier', 'customer type', 'key account', 'key accounts', 'strategic accounts')
comment = 'KEY ACCOUNT = balance of 5,000 USD or more. CREDIT HOLD = negative balance. STANDARD = everyone else.',
customer.customer_status as iff(account_balance_usd < 0, 'CREDIT HOLD', 'ACTIVE')
with synonyms = ('status', 'active', 'active customer')
comment = 'ACTIVE customers are all customers not on credit hold.',
nation.customer_region as case region_id when 0 then 'AFRICA' when 1 then 'AMERICA'
when 2 then 'ASIA' when 3 then 'EUROPE' when 4 then 'MIDDLE EAST' end,
lineitem.ship_date as ship_date with synonyms = ('revenue date')
comment = 'Revenue is recognized on this date: use it for revenue by year, quarter or month.',
...
)
metrics (
lineitem.net_revenue as sum(lineitem.net_revenue_amount)
with synonyms = ('revenue', 'net sales', 'sales', 'total revenue'),
lineitem.on_time_delivery_rate as sum(lineitem.is_on_time) / count(lineitem.line_number)
with synonyms = ('on-time rate', 'OTD', 'delivery performance', 'SLA attainment')
comment = 'Share of lines received within the grace period. Report by receipt_year or receipt_date.',
customer.active_customer_count as count(iff(customer.account_balance_usd >= 0, customer.customer_id, null)),
...
)当我们可视化其组件和关系时,这个语义模型是这样的:

注意其中有哪些是任何INFORMATION_SCHEMA查询都无法返回的:阈值、宽限期、负余额的含义、退货要从收入中扣除这一事实,以及业务所使用的全部术语。
Cortex Analyst用一次请求就回答了每个问题,而且每个响应都以其解释开头,正是在这里你能看到规则在发挥作用:
- 净收入:“基于ship_year,1995日历年的net_revenue_amount之和。”结果:$261,551,342,124.97。
- 关键客户:“1996年所下订单中账户余额为5,000美元或以上的客户。”结果:$156,886,051,998.23。
- 准时率:“1996年收到的行……按receipt_year报告。”结果:按模式为39.16%至39.30%。
- 活跃客户:“BUILDING市场细分中账户余额 >= 0。”结果:273,075。
六题全对,每个问题一次Analyst调用和一次仓库查询,Analyst延迟为四到五秒,执行时间不到一秒。为了进一步说明这一点,以下是关键客户问题的整个“对话”,而在无辅助的情况下,代理用了十二次调用和一个捏造的定义:
SELECT * FROM SEMANTIC_VIEW(
DEMO_TPCH.SEMANTICS.OPERATIONS_ANALYTICS
METRICS total_order_value
DIMENSIONS customer.customer_tier
WHERE customer.customer_tier = 'KEY ACCOUNT' AND sales_order.order_year = 1996
)错误答案的共同点

没有上下文时,代理大部分时间都在探索INFORMATION_SCHEMA,最终只答对了33%。有了上下文,它答对了全部六个问题。
“反对!”你说。“六个只是小样本。” 确实。但它包括了收入数字、关键客户数字、SLA数字和活跃客户数;通过的两个是控制项。随便挑你利益相关者上个季度问过的四个问题,看看有多少依赖一个能干净地落在单列中的定义。
看看代理在哪里出错:
- 未定义的业务术语。 “关键客户”、“活跃客户”、“准时”。这些词出现在问题中,却不在数据库的任何地方。LLM必须发明一个定义,而它确实会这么做。
- 有歧义的指标。 单张表中的“收入”可以是四个不同的数字:毛收入、折扣后净值、折扣和税后净值,或订单表头总额。等等,退货算不算?
- 名称驱动的推断。 一个名为DISCOUNT_PERCENT的列会被除以100。没有人告诉LLM这些值已经是分数。
- 每个问题都要重新发现。 没有共享上下文时,LLM每次都从头读取模式,token账单不断累积。
如果这些听起来很熟悉,你可能正在从事数据工作。
而这个练习无法模拟的是:达成定义本身就是增值。有了虚拟数据,创建虚拟语义就像编写SQL一样容易。在现实世界中,就活跃客户达成一致定义,需要与多个业务团队接触、定义来源、粒度、调和相互冲突的意见、获得签字确认,所有这些都发生在语义视图进入画面之前。
换句话说:数据建模。
模型就是入职培训
想想你上一次入职的分析师。第一天,他们能访问仓库,除此之外没什么别的。你是把凭据交给他们然后就走开了吗?当然不是。你告诉了他们一些事。净收入是毛收入减去折扣减去退货,不含税,我们在发货日期确认。关键客户是任何余额为五千或以上的人。诸如此类。
把LLM想象成世界上最好的分析师,昨天刚被录用。它对你的组织一无所知,但与人不同——人会在不确定时犹豫——它会选择最可能的解释并自信地回应。
人类和LLM都需要上下文。语义视图就是与新分析师第一天的对话,被写了下来;而写下来甚至还不是最难的部分。让业务利益相关者达成一致并在规模上维护它,才是真正的工作所在。
直白地说,数据模型是你AI的世界观,而其答案的质量受它所接收上下文的限制。
结论
现在,让我们把token和Snowflake积分成本加起来,看看“之后”的场景效率高出多少。当然,这恰好是完全错误的结论。
再看看那四条击垮无辅助代理的规则。净收入。关键客户。准时。活跃。这些并不稀奇。每家公司都有,而且在大多数公司里,它们所在的位置与这个演示中完全一样:在那些待得最久的人头脑里。
当然,最小化token很重要,但远不如把问题搞对重要。建造办公楼时,在混凝土中放置钢筋的ROI是多少?哦,大约一切!没有它,大楼就会倒塌。
你的数据和语义模型在你的AI战略中扮演同样的角色。没有上下文,AI就像没有钢筋的混凝土:看起来坚固,但在任何真正的考验下都会开裂——一次一个看似合理的错误答案。
我的书,如果你想通过为Snowflake编写的实用示例学习核心数据建模概念:
本文提到的相关文章:
引用的Snowflake文档:
- https://docs.snowflake.com/en/user-guide/views-semantic/overview
- https://docs.snowflake.com/en/sql-reference/sql/create-semantic-view
- https://docs.snowflake.com/en/user-guide/snowflake-cortex/cortex-analyst
- https://docs.snowflake.com/en/sql-reference/account-usage/cortex_ai_functions_usage_history
- https://www.snowflake.com/legal-files/CreditConsumptionTable.pdf
自信地犯错:我让一个 AI 分析师在 Snowflake 上自由发挥——在有和没有语义模型的情况下 最初发表于 Snowflake Builders Blog:数据工程师、应用开发者、AI 与数据科学 在 Medium 上,人们通过突出显示并回应这个故事来继续对话。
这篇内容对你有用吗?
反馈只用于改善内容筛选,不等同于收藏