☰
指标语义模型标准规范:度量、维度与切片在 YAML 中的声明与大模型接入
2026/10/10 3:50:52 网站建设 项目流程

“玲姐,数仓治理小组和算法团队又掐起来了。数仓觉得在数仓表注释里写上‘这是支付金额’就够了;算法团队却要求数仓把每一个维度的计算公式写成五层嵌套的 JSON Schema,数仓开发看了一眼直接摔鼠标不干了!”

周一早晨十点,会议室门还没开,助理小赵就跑来打小报告。我家英短猫 Null 趴在我的工位文件架顶层,冷眼看着路过争论得面红耳赤的两个团队负责人。

很多企业在搞大模型 ChatBI 时,总会陷入这种“两极割裂”:要么把一切希望寄托在大模型的“神奇理解力”上,任由大模型面对裸 DDL 猜天猜地;要么设计出一套极其繁复反人类的元数据规范,把一线数仓工程师活活逼疯。

要让大模型能够像一个拥有五年经验的老分析师一样精准取数,关键是搭建一套人机双赢的指标语义模型标准规范。人写起来清晰自然,大模型读起来零歧义。而 YAML,正是连接这两端最完美的介质。


为什么指标语义层必须独立于物理表

在传统数仓中,我们的数据建模是“物理表中心制”的:ODS $\to$ DWD $\to$ DWS $\to$ ADS。
但业务人员提问从来不以表为中心。业务问的是:

  • “上周每个省份的复购率是多少?”
  • “新用户的笔单价环比涨了还是跌了?”

如果直接让大模型读表 DDL:

  1. 维度爆炸与口径黑洞:同一个“省份”,在用户表里是buyer_province,在发货表里是sender_province,在收货表里是receiver_province。大模型到底连哪一个?
  2. 度量聚合不可逆:笔单价 = 总金额 / 总单数。很多初级数仓开发喜欢在 DWS 层直接建一个avg_order_amt字段存平均值。当下游需要按周、按月或跨区域重新汇总时,平均值直接累加在统计学上是严重的致命错误!

因此,指标语义模型(Semantic Model)的核心价值在于:把“度量(Measures)”、“维度(Dimensions)”与“计算规则(Derivations)”与物理物理表彻底解耦。大模型只负责理解业务语义概念,编译器负责将语义概念映射到物理执行 SQL。


语义规范标准切片:Cube 与 Metrics 的 YAML 规范设计

我们团队在经历了多次线上真实对齐演习后,沉淀出了一套极其抗造的语义声明规范。

整个规范围绕三个核心实体构建:

  • Data Source(数据源事实抽象)
  • Dimensions(切片与下钻轴)
  • Measures & Metrics(原子度量与派生指标)

以下是交易域语义规范的核心定义切片:

version: "semantic.v3" model_name: "trade_analytics_cube" display_name: "电商核心交易分析模型" description: "涵盖全平台订单交易、支付、退款及买家多维切片核心事实" # 底层物理表映射(隔离物理变动) source: database: "dw_prod" table: "dwd_trade_order_detail_di" primary_key: "order_detail_id" incremental_filter: "dt >= '2026-01-01'" # 维度定义:业务切片的骨架 dimensions: - id: order_time display_name: "下单时间" column: "create_time" type: "time" time_grains: ["day", "week", "month", "quarter"] description: "用户最终提交订单的系统物理时间戳" - id: buyer_region display_name: "买家收货大区" column: "receiver_region" type: "categorical" cardinality: 8 sample_values: ["华东", "华北", "华南", "西南", "华中"] description: "按照国家标准行政区域划分的收货所在地理大区" - id: is_new_buyer display_name: "是否新客首单" expression: "CASE WHEN buyer_order_seq = 1 THEN '是' ELSE '否' END" type: "boolean" description: "买家在平台全生命周期内完成的第一笔支付订单打标" # 度量与指标定义:数值聚合的灵魂 measures: # 1. 原子度量(不可再分的基础聚合单元) - id: raw_gmv display_name: "总成交金额(分)" expression: "SUM(item_pay_amount)" aggregation: "sum" data_type: "decimal" default_filter: "order_status IN (2, 3, 4) AND is_test = 0" description: "已付款成功的商品实付明细金额汇总,排除测试商户流水" - id: distinct_buyers display_name: "支付用户数" expression: "COUNT(DISTINCT buyer_id)" aggregation: "count_distinct" data_type: "integer" default_filter: "order_status IN (2, 3, 4) AND is_test = 0" description: "有过至少一次成功支付事件的去重买家数" - id: total_order_count display_name: "成交主单数" expression: "COUNT(DISTINCT order_id)" aggregation: "count_distinct" data_type: "integer" description: "成功支付的交易主订单去重汇总" # 2. 派生与复合指标(运行时按需展开) metrics: - id: customer_arpu display_name: "买家客单价(元)" formula: "raw_gmv / NULLIF(distinct_buyers, 0) / 100.0" dependencies: ["raw_gmv", "distinct_buyers"] format: "currency" description: "平均每个去重支付买家所贡献的交易净额(单位:元)" - id: item_basket_size display_name: "单均件数" formula: "SUM(buy_quantity) / NULLIF(total_order_count, 0)" dependencies: ["total_order_count"] format: "decimal_2" description: "平均每个主订单中包含的商品购买件数"

让大模型理解指标语义:Prompt 瘦身与动态注入机制

大模型的上下文窗口是极其宝贵且昂贵的。很多系统之所以 Text2SQL 响应慢且经常出现幻觉,就是因为试图把上百张表、上千个指标定义全部一股脑塞进 System Prompt。

在我们的 ChatBI 引擎中,大模型接入分为三级动态装配流程:

[用户自然语言提问: "看下华东新客上周的客单价"] | v +-------------------------------------------------------------+ | 1. 向量相似度与实体召回 (Semantic Search & BM25) | | - 意图识别关键词: "客单价", "华东", "新客", "上周" | | - 从 YAML 库中精准召回: customer_arpu, buyer_region, ... | +-------------------------------------------------------------+ | v +-------------------------------------------------------------+ | 2. 上下文即时编译 (Prompt Micro-Injection) | | - 仅提取命中的指标与维度及其计算依赖,序列化为紧凑 Schema | +-------------------------------------------------------------+ | v +-------------------------------------------------------------+ | 3. 大模型意图抽取 (Structured Output JSON) | | - 提取: metric=customer_arpu, filters=[region, is_new] | +-------------------------------------------------------------+ | v +-------------------------------------------------------------+ | 4. 确定性语义编译器 (Deterministic SQL Compiler) | | - 展开原子指标与四则运算,输出零错误物理 SQL | +-------------------------------------------------------------+

实际喂给大模型的紧凑上下文示例

大模型在处理上述查询时,接收到的并非庞大的数仓 DDL,而是一个极其结构化的微型定义包:

{ "target_metric": { "id": "customer_arpu", "name": "买家客单价(元)", "required_dimensions": ["order_time", "buyer_region", "is_new_buyer"], "allowed_time_grains": ["day", "week", "month"] }, "constraints": { "default_filter": "order_status IN (2, 3, 4) AND is_test = 0" } }

大模型只需要专注于将用户的口语化表达(“上周”、“华东”、“新客”)对应提取成参数值:

{ "selected_metric": "customer_arpu", "time_grain": "week", "time_range": {"relative": "last_week"}, "dimension_filters": [ {"dimension": "buyer_region", "operator": "=", "value": "华东"}, {"dimension": "is_new_buyer", "operator": "=", "value": "是"} ] }

语义编译器:确保 100% 口径闭环的最后防线

大模型吐出上面的结构化 JSON 后,系统绝对不会把“写出最终 SQL”的任务再次丢回给大模型去掷骰子,而是由后台确定性的 Python 语义编译器接管:

class MetricQueryCompiler: def __init__(self, yaml_schema: dict): self.schema = yaml_schema def build_sql(self, intent_json: dict) -> str: metric_id = intent_json["selected_metric"] # 查找指标定义 metric_def = next(m for m in self.schema["metrics"] if m["id"] == metric_id) # 1. 自动提取依赖原子度量 formula = metric_def["formula"] source_table = self.schema["source"]["table"] # 2. 提取维度与过滤条件 where_conditions = [self.schema["source"]["incremental_filter"]] for f in intent_json["dimension_filters"]: dim_def = next(d for d in self.schema["dimensions"] if d["id"] == f["dimension"]) expr = dim_def.get("expression", dim_def.get("column")) where_conditions.append(f"{expr} {f['operator']} '{f['value']}'") # 3. 展开原子度量内部的默认过滤逻辑 where_clause = " AND ".join([f"({c})" for c in where_conditions]) # 4. 生成具备防御性除以零保护的生产 SQL sql = f""" SELECT '{metric_id}' AS metric_name, {formula} AS metric_value FROM {source_table} WHERE {where_clause} """ return sql

总结:指标治理是大模型落地的第一生命线

别再指望抛给大模型几百行残缺不全的建表语句,就能奇迹般解决企业长达十年的口径混乱问题。

大模型不是魔术师,它是一个极度聪明但容易被噪音误导的计算引擎。用声明式 YAML 构建清晰的指标语义模型,把度量、维度与切片规则白纸黑字地锁死在代码仓库中,大模型才能真正卸下“猜测表结构”的沉重包袱,成为业务部门即问即答、信马由缰的数据智囊。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询