← 全部文章

让分析 agent 先过考试:拆开 Data Culture 在 dbt 上的评测框架

正面拍的一台白色综合验光仪,左右两组镜片拨盘对称排开,每个圆孔周围都是刻度环和金属旋钮,背景是浅灰色
封面 · 来源: João Melo / Unsplash
目录
  1. 同一个模型,准确率从 0 走到 94.7%
  2. 一张图看全:五层,从数据到成绩单
  3. agent 只看一份契约:AGENTS schema 里的四张表
  4. 考卷怎么出:两个分析师会写出同一条 SQL 的题
  5. 判分从严到宽,三关都没过才请裁判
  6. 成绩单要能告诉你下一层该补什么
  7. 和 Anthropic 的内部做法对照:方向一致,细节不同
  8. 照着搭:哪些直接借,哪些要改

分析 agent 接上数仓以后,第一个该回答的问题是“它答对了多少”。Data Culture 第一次给客户上线时答不上来:团队把 agent 接到 Snowflake 的 MCP 服务器上给测试用户用,连一个大概的准确率都说不出。1之后他们定下一条规矩:没法量的,不上线。这个做法借自 Chip Huyen 在《AI Engineering》里讲的“评测驱动开发”。1

dbt Labs 10 月 26 日的博客记录了 Data Culture 分析工程总监 Opeyemi Fabiyi 在 dbt Summit 2026 上的分享;团队把评测管道开源成了 agent-trust-for-dbt 仓库。12这篇把仓库拆成五层,一层一层讲。读完应该能照着搭一个自己的版本。

同一个模型,准确率从 0 走到 94.7%

电商客户的项目里,模型从头到尾没换,考卷和判分程序也没换,每次只多加一层上下文:1

FIG.1每加一层上下文,准确率涨多少。模型和考卷都没换,数据来自 Data Culture 在 dbt Summit 2026 上的分享。

直接查原始数据时一道都答不对;换成 dbt 建模好的表,到 50%;再依次加上列描述、业务规则和默认过滤条件、已验证查询和命名指标,最后到 94.7%。医疗客户也从 0% 起步,加了同类的业务规则和已验证查询之后,过了 Data Culture 内部 90% 的上线线;原文没给中间的数。1

这组数有两种读法。第一,列描述只加了 2.6 个点,业务规则加了 26.3 个点。agent 缺的主要是这家公司内部怎么算,列名说明帮得有限。第二,52.6%、78.9%、94.7% 恰好等于 10/19、15/19、18/19,考卷很可能只有 19 道题左右,一道题约 5.3 个百分点。这是我从百分比倒推的,原文没给题数;50% 用 19 除不出来,那一级的题数可能不同。题这么少的时候,两个版本差一两道题,就不该当成真的变好或变坏。

一张图看全:五层,从数据到成绩单

仓库 README 里有一张结构图,我照着它重画成五层:2

FIG.2agent-trust-for-dbt 的五层。评测层是这套框架的核心;最下面一层只有文档,没有代码。根据仓库 README 的结构图重画。
  1. 数据底座。 用公开的 Olist 巴西电商数据,跑在 DuckDB 上。dbt 项目分 staging、intermediate、marts 三层,只有打了 agent 标签的 marts 对 agent 开放。
  2. 契约层。 一个叫 AGENTS 的 schema,用四张表告诉 agent 能查什么、怎么算。
  3. agent 层。 Claude 或其他大模型通过 DuckDB 的 MCP 服务器读契约、写 SQL,交回答案、SQL 和查询结果。
  4. 评测层。 40 道金标准问题逐一跑过 agent,先做三关确定性比对,都没过再请 LLM 裁判,最后出成绩单。
  5. 综合方法。 只有文档:什么时候自动判、什么时候交给人,怎么拿专家过去的分析做对照,怎么把纠错写回去,以及这套办法抓不到什么。

仓库自己写明,这是一份教学用的参考实现,不是生产评测系统,客户数据也不在里面。2下面重点展开第 2 层和第 4 层,它们最值得照抄。

agent 只看一份契约:AGENTS schema 里的四张表

契约由 dbt 宏 build_agents_schema 生成,一共四张表:2

  • agents.root:业务口径。仓库里放的是一份 olist_business_context.md,写明“收入”默认指 GMV(已完成订单的商品金额,不含运费),哪些订单状态算完成,时间要先从 UTC 转成圣保罗时间再按天、按月截断,数人头要用 customer_unique_id,以及哪些问题超出范围(成本、利润、网站流量、预测)。
  • agents.dbt_model:只登记打了 agent 标签的 marts,staging 和 intermediate 不在里面。
  • agents.dbt_column:这些表每一列的类型和说明。
  • agents.dbt_dependency:表之间的血缘。
FIG.3上面是提示词规定的读取顺序,下面是代码里的闸门。能查哪张表,最终由校验器按 dbt_model 的登记决定。

agent 启动时先检查 agents.root 里有没有业务口径,没有就报错,不启动。提示词规定读的顺序:先读 root,再看 dbt_model 挑粒度合适的表,再读要用的列,最后只查 marts。它只能用自己真正跑过的查询作答,问题超出范围就拒答并说明原因。2

只靠提示词不够,代码里还有一道闸。每条 SQL 执行前都要过校验:只允许一条 SELECT;检查查询的逻辑计划,引用的表必须登记在 agents.dbt_model 里,read_csv 这类直接读文件的函数会被拦下。2这里有个小矛盾:提示词说不许查 agents schema,校验器却放行它,因为 agent 要读契约。文字比代码严,照搬时最好把两边统一。

业务规则放在哪里也有讲究。地区划分、部门归类、去重这些规则,在 intermediate 模型里算一次,agent 看到的是已经算好的列。2这和图 1 里 78.9% 那一级对得上:规则写进模型和口径文档,agent 就不用每次自己推。

考卷怎么出:两个分析师会写出同一条 SQL 的题

金标准问题的标准是:两个分析师读到这道题,会写出基本相同的 SQL,得到相同的结果。每道题的期望答案先由业务专家确认过才能用。1仓库的指南把它拆成几条:3

  • 用业务人员的说法,不把列名写进题里;
  • 一道题只考一件事,并且来自真实需求;
  • 期望 SQL 要实际跑过,请业务专家或数据负责人确认;
  • 日期写死,不用“上个月”这种会变的说法;
  • 期望 SQL 只返回验证答案需要的列,多一列就会造成误判。

仓库的 40 道题按类型分七类。下面每类举一道仓库里的原题,括号里是难度:2

  • 聚合,9 道:“2018 年第一季度的 GMV 是多少?”(简单)
  • 比率,10 道:“2018 年 3 月的客户满意度(CSAT)是多少?”(简单)
  • 多指标,7 道:“2017 年第四季度,州内订单和跨州订单的平均配送时间各是多少?”(中等)
  • 时间序列,6 道:“2017 年每个月新增了多少客户?”(中等)
  • 过滤,3 道:“2017 年有多少订单被取消?”(简单)
  • 排名,2 道:“2017 年 GMV 最高的 10 个商品品类是哪些?”(简单)
  • 越界,3 道:“能预测一下 2019 年第一季度的 GMV 吗?”(简单)。这类题的期望 SQL 留空:数据里没有预测,agent 应该拒答。

难度分三档:简单 14 道,一张表、一次聚合、一个明确的过滤条件;中等 17 道,涉及多张表、日期逻辑、地区分组或一条业务规则;困难 9 道,要用窗口函数、跨期对比、帕累托分析,或同时用上几条业务规则。23

指南建议从 20 到 40 道起步,质量优先。用户问到没覆盖的就加题,口径或数据模型变了就改期望 SQL 并重新确认,过时的题直接退掉。3

判分从严到宽,三关都没过才请裁判

Data Culture 最早的评测用精确匹配,人工翻结果时发现,一批“错题”其实是对的:数字对,只是列名不同;三个品类都对,多了一行合法的“未分类”;数字对,多带了一列辅助数据。1于是他们改为比较查询结果,不再比较 SQL 文本。

FIG.4eval_pipeline.py 的判分顺序。前三关是确定性的,可以重放;裁判只处理剩下的题。拒答题在进入这四关之前单独判。

仓库的 eval_pipeline.py 先处理拒答:越界题拒答算对;越界题却给了答案、能答的题却拒答,都算错。2剩下的题依次过三关:

  1. 严格比对。 列名和行都排好序,值转成文本算 MD5,一致就过。
  2. 不看列名。 行数相同时去掉列名,只比每一行的值。
  3. 允许误差。 数字差在 0.01 加 0.1% 以内算相等,文字忽略大小写和首尾空格。

三关都没过、两边都有结果时,才交给 LLM 裁判。裁判看两份结果(各最多 20 行),判断“对提问的人来说,这两份结果给出的是不是同一个答案”,打 0 到 1 分,0.8 分及以上算过;默认裁判模型是 claude-sonnet-4-6。2按原文的说法,确定性比对能处理约 80% 的题,快、免费、可以重放;剩下约 20% 交给裁判。1

文字和代码在这里有三处出入,照搬前要知道:12

  • 原文提到有一级“容忍多出来的列”,代码里多一列或多一行都会直接判错,只能靠裁判救回来。
  • README 说裁判给出 PASS 或 FAIL,外加一句理由;代码里只有一个分数,没有理由。
  • 原文给的开源链接写成 dbt-agent-trust,实际仓库名是 agent-trust-for-dbt。

我按代码讲,因为跑出来的成绩由代码决定。

成绩单要能告诉你下一层该补什么

每道题记一行:问题、类别、难度、期望 SQL、agent 写的 SQL、有没有作答、过没过、在哪一关过的、裁判分、token、成本和耗时。汇总时分别算总通过率、可答题的通过率、越界题的处理,以及按关卡、难度、类别拆开的通过数。2

成绩单的用处是决定下一步补哪一层。比率题集中挂掉,多半是口径没写清;时间序列题挂掉,先查时区和每周从哪天算起;靠裁判才过的题多,说明期望 SQL 返回的列可能不够干净。补完一层,用同一套题重跑;一次只改一样,涨跌才能归到那一层头上。图 1 的五个台阶,就是这样一级一级量出来的。1

和 Anthropic 的内部做法对照:方向一致,细节不同

Anthropic 的数据团队 6 月发过一篇文章,讲内部怎么用 Claude 做自助分析:不加 skills 时,准确率在他们的评测上没超过 21%;加上之后整体超过 95%,部分领域接近 99%。4两家的做法放在一起看:

FIG.5两套做法并排。分歧最大的是历史查询:经人确认的少量查询有用,未经整理的大量查询几乎没用。资料来自 dbt 博客和 Anthropic 的文章。

两边一致的地方有三点:准确率主要是上下文问题;同一套题,一次只改一处;越界和拒答要单独测。

分歧在历史查询上。Data Culture 的最后一级“已验证查询和命名指标”带来 15.8 个点。Anthropic 发现,让 agent 直接 grep 几千个历史 SQL 文件,准确率变化不到 1 个点,他们建议把查询库提炼成文档和 skills。4合起来看,经人确认、数量有限的查询有用,未经整理的一大堆历史查询几乎没用。Anthropic 还试过让大模型自动生成指标定义,在评测上反而拖分,他们的结论是让 Claude 起草文档,定义由人来定。4这和 Data Culture 让专家确认每道题的答案是同一个思路。

另一个提醒来自维护。Anthropic 上线时离线准确率约 95%,一个月后掉到约 65%,原因是表和口径变了,文档没跟上。4Data Culture 的 94.7% 只是某一天的快照,考卷和契约如果不随数据模型一起维护,这个数也会掉。

公开榜单的分数同样不能直接搬过来。Spider 2.0 用 632 道企业级任务测 text-to-SQL,当时最好的 agent 只做对 21.3%,同样的配置在老的 Spider 1.0 上是 91.2%。5换到自己的仓库,要有自己的考卷。

照着搭:哪些直接借,哪些要改

可以直接借的:

  • AGENTS 契约表加只读 SQL 校验。 agent 能查什么由 dbt 标签决定,口径写在一份由人维护的文档里。
  • 按结果比对的三关,加上拒答题。 确定性的部分越多,评测越便宜,也越能重放。
  • 按关卡和类别拆开的成绩单。 用它决定下一层补什么。

要按自己的情况改的:

  • 数字容差。 0.01 加 0.1% 对金额可能太松,对比率可能太严,按指标分别定。
  • 多出来的列和行。 如果业务能接受 agent 多给一列辅助数据,把这条规则写进比对代码,不要全交给裁判。
  • 裁判只给分、不给理由。 审计场景要把理由、两份原始结果和裁判模型的版本一起存下来,否则事后没法复核。这和我在裁判模型那篇里提的要求一样:结论要能重放。
  • 题量和上线线。 19 道题时一道就是 5 个多点。“过 90%”最好同时写明最少题数,以及每一类至少几道。

先写考卷,再接 agent。考卷写好以后,换模型、加上下文、改口径,每一步的涨跌都能量出来。

参考

  1. How Data Culture measured its way from zero trust to a shippable AI analytics agent, dbt Labs Blog
  2. agent-trust-for-dbt, GitHub
  3. Golden question guide, GitHub
  4. How Anthropic enables self-service data analytics with Claude, Claude Blog
  5. Spider 2.0: Evaluating Language Models on Real-World Enterprise Text-to-SQL Workflows, arXiv

评论