Ch 40 语义平面:三层治理与 Git+ YAML¶
面包屑
本书主页 › Part VII Data+AI 转型 › Ch 40
项目第 4 年 · Data+AI转型期——语义平面
本章你将学到¶
- 三层治理:L1 元数据契约 / L2 术语治理 / L3 业务规则,以及对应的 YAML 资产示例
- 资产类型体系(9 种,含 Join/Few-shot YAML)与 Git+YAML 选型理由
- System Prompt 设计:分层模板 + few-shot 分级注入 + 术语绑定强路由
- 离线发布管线设计(含 CI 引用完整性校验伪代码)
- 对比 dbt Semantic Layer / Cube 的语义层设计
40.1 三层治理:L1 元数据契约 / L2 术语治理 / L3 业务规则¶
Ch 39 把 Agentic BI 的骨架讲完了,"语义平面"是其中最让我兴奋的部分——它是治理层的心脏。这章就把它拆开来看,说到底也不复杂:就是把"人的业务知识"编码成"机器能读懂的约束"。
专利数据领域我干过类似的活。做专利检索,系统得会认同义词——用户敲"锂电池",它要知道这也是"二次电池"、"储能器件"。我们当时搞了一个"专利术语词典",把同义词、上下位词、相关词全编码成机器可读的映射。Agentic BI 的语义平面就是这个思路的升级版,术语映射只是底子,上面还有指标定义、join 路径、业务规则,拼成一套机器可读业务知识库。
但语义平面真正的难点不是"设计"——是"维护"。谁来写这些 YAML?怎么保证它们写得对?变更怎么受控?这些比技术方案本身更难搞定,也是这章花力气讲的部分。
治理 vs 元数据¶
普通元数据(如 information_schema)只告诉你"表结构长什么样"——列名、类型、可空。语义治理多了一层:这个字段在业务上到底啥意思、该怎么用、跟别的字段啥关系——这些 LLM 光靠世界知识补不出来,是企业自己才有的东西。把治理当"一等公民"来对待,原因就在这里:
| 维度 | 普通元数据(information_schema) | 语义治理(NewtonData 语义平面) |
|---|---|---|
| 描述内容 | 表结构(列名/类型/约束) | 业务含义(指标定义/术语映射/join 路径/规则) |
| 来源 | DDL 自动生成 | 人工编写 + CI 校验 |
| 消费者 | BI 工具、ETL 工具 | LLM Agent(RAG 检索) |
| 变更频率 | 随 DDL 变更 | 低频高治理(PR 审查) |
| LLM 能否补全 | 能(通用知识) | 不能(企业特有知识) |
表 40-1 治理 vs 元数据
引申:基石回扣——从被动血缘到主动语义资产
语义平面是 Ch 20 元数据管理的自然演进。Ch 20 的被动血缘——审计日志、batch_id 关联、Glue Catalog——只能"事后追溯数据怎么来的",是描述性的,回答的是"数据是什么、从哪来"。语义平面不一样,它是"主动的、机器可读的业务知识",是规范性的,回答"数据应该怎么用、业务上意味着什么"。Ch 20 把"主动血缘"列为演进目标(Ch 52 里做了复盘),语义平面就是这个目标在 AI 消费侧的落地:Git+YAML 版本化的语义资产,就是 Ch 20 想要的那个"主动、版本化、可审查的元数据层"。
%%{init: {'theme':'base','themeVariables':{'primaryColor':'#edf5ff','primaryTextColor':'#161616','primaryBorderColor':'#0f62fe','lineColor':'#697077','secondaryColor':'#d9fbfb','tertiaryColor':'#f2f4f8','fontSize':'14px'}}}%%
flowchart TB
subgraph 三层治理[“语义平面三层治理”]
L1[L1 元数据契约<br/>表/列/指标/ join / few-shot 定义<br/>机器可读的结构化描述]
L2[“L2 术语治理<br/>业务术语→技术资产映射<br/>如 “GMV”→metric_gmv”]
L3[“L3 业务规则与上下文<br/>计算规则/约束/适用场景<br/>如 “GMV 不含退货订单””]
end
L3 -->|规则约束|L2
L2 -->|术语绑定|L1
L1 -->|供检索|RAG[R/V/G/D 四引擎]
linkStyle default stroke:#697077,stroke-width:2px
classDef bpProcess fill:#edf5ff,stroke:#0f62fe,stroke-width:2px,color:#161616
classDef bpData fill:#d9fbfb,stroke:#007d79,stroke-width:2px,color:#161616
classDef bpDecision fill:#fcf4d6,stroke:#f1c21b,stroke-width:2px,color:#161616
classDef bpSuccess fill:#defbe6,stroke:#198038,stroke-width:2px,color:#161616
classDef bpError fill:#fff1f1,stroke:#da1e28,stroke-width:2px,color:#161616
classDef bpExternal fill:#f2f4f8,stroke:#697077,stroke-width:2px,color:#161616
classDef bpInfo fill:#f6f2ff,stroke:#8a3ffc,stroke-width:2px,color:#161616
classDef bpGroup fill:#ffffff,stroke:#0f62fe,stroke-width:2px,color:#161616
class L1 bpData
class L2 bpInfo
class L3 bpInfo
class RAG bpProcess
图 40-1 三层治理:L1 元数据契约 / L2 术语治理 / L3 业务规则
| 层 | 职责 | 举例 |
|---|---|---|
| L1 元数据契约 | 表/列/指标/join 的结构化定义 | table: fact_prescription, columns: [...] |
| L2 术语治理 | 业务术语→技术资产映射 | term: "GMV" → metric: metric_gmv |
| L3 业务规则 | 计算规则/约束/上下文 | rule: GMV = SUM(amount) WHERE status='completed' |
表 40-2 三层治理:L1 元数据契约 / L2 术语治理 / L3 业务规则
三层的关系¶
%%{init: {'theme':'base','themeVariables':{'primaryColor':'#edf5ff','primaryTextColor':'#161616','primaryBorderColor':'#0f62fe','lineColor':'#697077','secondaryColor':'#d9fbfb','tertiaryColor':'#f2f4f8','fontSize':'14px'}}}%%
flowchart LR
TERM[“业务术语”GMV””] -->|L2 绑定|METRIC[指标 metric_gmv]
METRIC -->|L1 定义|TABLES[涉及表 fact_orders + dim_status]
RULE[“L3 规则<br/>”GMV 不含退货””] -->|约束|METRIC
linkStyle default stroke:#697077,stroke-width:2px
classDef bpProcess fill:#edf5ff,stroke:#0f62fe,stroke-width:2px,color:#161616
classDef bpData fill:#d9fbfb,stroke:#007d79,stroke-width:2px,color:#161616
classDef bpDecision fill:#fcf4d6,stroke:#f1c21b,stroke-width:2px,color:#161616
classDef bpSuccess fill:#defbe6,stroke:#198038,stroke-width:2px,color:#161616
classDef bpError fill:#fff1f1,stroke:#da1e28,stroke-width:2px,color:#161616
classDef bpExternal fill:#f2f4f8,stroke:#697077,stroke-width:2px,color:#161616
classDef bpInfo fill:#f6f2ff,stroke:#8a3ffc,stroke-width:2px,color:#161616
classDef bpGroup fill:#ffffff,stroke:#0f62fe,stroke-width:2px,color:#161616
class TERM bpInfo
class METRIC bpData
class TABLES bpData
class RULE bpInfo
图 40-2 三层的关系
引申
三层治理说到底就是把业务知识分层编码。L1 是"数据长什么样"(结构),L2 是"业务术语对应什么"(映射),L3 是"怎么算才对"(规则)。三层各从不同维度收缩 LLM 的搜索空间——LLM 不是在"整个数仓里猜",而是在被约束好的语义空间里推。
把上面表格里的 GMV 例子落到 YAML,三层资产各是一个文件,相互引用、由 CI 校验引用完整性:
# 示意:L1 元数据契约——Table 资产(fact_orders 的结构化描述)
# 文件:semantic-assets/tables/fact_orders.yaml
asset_type: table
name: fact_orders
description: "订单事实表,记录每笔销售订单"
columns:
- name: order_id
type: string
role: primary_key
sensitivity: P2
- name: product_id
type: string
role: foreign_key
references: dim_product.product_id
- name: amount
type: decimal
description: "订单金额(含税)"
- name: status
type: string
description: "订单状态:completed/returned/cancelled"
grain: "一行 = 一笔订单"
# 示意:L2 术语治理——Term 资产(业务术语 "GMV" → 技术资产映射)
# 文件:semantic-assets/terms/gmv.yaml
asset_type: term
name: GMV
synonyms: ["成交额", "总销售额", "Gross Merchandise Volume"]
binding:
asset_type: metric
asset_name: metric_gmv # 核心意图:业务术语→指标,强路由绑定
description: "Gross Merchandise Volume,已成交订单的商品总额"
# 示意:L3 业务规则——Metric + Rule 资产(GMV 怎么算才对)
# 文件:semantic-assets/metrics/metric_gmv.yaml
asset_type: metric
name: metric_gmv
base_table: fact_orders
expression: "SUM(amount)"
filter: "status = 'completed'" # 核心意图:L3 规则约束——GMV 不含退货/取消订单
dimensions: [product_id, region, biz_date]
rules:
- type: exclude_status
values: ["returned", "cancelled"]
reason: "GMV 仅统计已成交订单"
这三份 YAML 经 CI 校验后发布——term: GMV 引用的 metric_gmv 必须存在(引用完整性),metric_gmv 引用的 fact_orders.amount/status 列必须在 L1 Table 资产中存在(Schema 一致性)。任何一处断链都会被 CI 阻断发布。
40.2 资产类型体系与 Git+YAML 选型¶
9 种资产类型¶
%%{init: {'theme':'base','themeVariables':{'primaryColor':'#edf5ff','primaryTextColor':'#161616','primaryBorderColor':'#0f62fe','lineColor':'#697077','secondaryColor':'#d9fbfb','tertiaryColor':'#f2f4f8','fontSize':'14px'}}}%%
flowchart TB
subgraph 资产类型["9 种语义资产类型"]
A1[表资产 Table<br/>表结构描述]
A2[列资产 Column<br/>列语义描述]
A3[指标资产 Metric<br/>指标定义与计算规则]
A4[Join 资产<br/>表间关联路径]
A5[术语资产 Term<br/>业务术语映射]
A6[规则资产 Rule<br/>业务约束规则]
A7[Few-shot 资产<br/>问答→SQL 示例]
A8[上下文资产<br/>适用场景描述]
A9@{ icon: "codicon:lock", form: "rounded", label: 权限资产<br/>数据访问策略, pos: "b", h: 36 }
end
linkStyle default stroke:#697077,stroke-width:2px
classDef bpProcess fill:#edf5ff,stroke:#0f62fe,stroke-width:2px,color:#161616
classDef bpData fill:#d9fbfb,stroke:#007d79,stroke-width:2px,color:#161616
classDef bpDecision fill:#fcf4d6,stroke:#f1c21b,stroke-width:2px,color:#161616
classDef bpSuccess fill:#defbe6,stroke:#198038,stroke-width:2px,color:#161616
classDef bpError fill:#fff1f1,stroke:#da1e28,stroke-width:2px,color:#161616
classDef bpExternal fill:#f2f4f8,stroke:#697077,stroke-width:2px,color:#161616
classDef bpInfo fill:#f6f2ff,stroke:#8a3ffc,stroke-width:2px,color:#161616
classDef bpGroup fill:#ffffff,stroke:#0f62fe,stroke-width:2px,color:#161616
class A1,A2,A3,A4 bpData
class A5,A6,A7,A8,A9 bpInfo
图 40-3 种资产类型
除了上面三层示例里提到的 Table/Term/Metric,Join 和 Few-shot 这两类资产对 NL2SQL 尤其关键——Join 告诉 LLM 表之间怎么连,Few-shot 让 LLM 看看"好的 SQL 长什么样":
# 示意:Join 资产(表间关联路径,供 Ch 43 Steiner 树消费)
# 文件:semantic-assets/joins/fact_orders_dim_product.yaml
asset_type: join
left_table: fact_orders
right_table: dim_product
condition: "fact_orders.product_id = dim_product.product_id"
cardinality: many_to_one
cost: 1.0 # 供查询规划器评估 join 代价
# 示意:Few-shot 资产(问答→SQL 示例,供 Ch 42 System Prompt 注入)
# 文件:semantic-assets/few-shots/gmv_by_region.yaml
asset_type: few_shot
complexity: simple # simple/medium/complex,决定注入数量
question: "华东区上月的 GMV 是多少?"
bound_terms: [GMV, 华东区, 上月]
sql: |
SELECT SUM(amount) AS gmv
FROM fact_orders
WHERE status = 'completed'
AND region = 'East China'
AND biz_date >= dateadd(month, -1, trunc(current_date, 'mon'))
9 类资产的运行时消费映射¶
9 种资产不是各管各的——运行时它们被 R/V/G/D 四引擎(Ch 41)以不同的路径消费。把这个映射理清楚,才能理解为什么需要 9 类而不是更少:
| 资产类型 | 层级 | 运行时消费方式 | 消费引擎 |
|---|---|---|---|
table_asset |
L1 | 向量检索表定义 | Engine V |
column_asset |
L1 | 别名精确匹配 + 向量检索 | Engine R+ / V |
metric_asset |
L1 | 向量检索 + 规划器源表推导 | Engine V / Ch 43 规划器 |
join_rule |
L1 | Steiner 树建图(join 路径规划) | Ch 43 规划器 |
few_shot_example |
L1 | 检索后注入 Prompt | Engine D / Prompt |
business_term |
L1+2 | 精确匹配 + 术语绑定强路由 | Engine R / 全链路 |
term_relationship |
L2 | Cypher 图遍历(同义/上下位) | Engine G |
business_rule |
L3 | 直接注入 Prompt + 护栏校验 | Prompt / Ch 44 |
business_context |
L3 | 直接注入 Prompt | Prompt |
表 40-5 9 类资产的运行时消费映射
ID 命名规范¶
资产 ID 用正则约束,保证全局唯一、可追溯——CI 强制校验命名规范:
| 资产类型 | ID 格式 | 示例 |
|---|---|---|
table_asset |
tbl_<domain>_<name> |
tbl_pharma_fact_prescription |
column_asset |
col_<table>_<column> |
col_fact_txn_price |
metric_asset |
metric_<name> |
metric_gmv |
business_term |
term_<name> |
term_gmv |
表 40-6 ID 命名规范
System Prompt 设计¶
语义资产最终得进 LLM 的 System Prompt,才能管住 SQL 的生成。一个设计良好的 System Prompt 是分层的:角色定义 → 数据库 schema 约束 → 安全规则 → 输出格式,再按查询复杂度分级注入 few-shot,术语绑定强路由优先:
# 示意:System Prompt 模板 + few-shot 分级注入 + 术语强路由
SYSTEM_PROMPT = """你是 Aurora CDP 的 NL2SQL 引擎,只输出 :simple-amazons3: Redshift SQL。
【数据库约束】只能引用语义资产中定义的表/列,禁止臆造。
【安全规则】禁止 DROP/DELETE/TRUNCATE;必须带 LIMIT;PII 列不可 SELECT。
【术语绑定】用户提到 "GMV" → 必须用 metric_gmv 的定义(SUM(amount) WHERE status='completed')。
【输出格式】仅返回 SQL,无解释。"""
def build_few_shot(query: str, complexity: str) -> str:
# 核心意图①:术语绑定强路由优先注入(GMV/华东区 等命中即注入对应 few-shot)
bound = match_terms(query) # 命中 GMV → gmv_by_region.yaml
shots = load_few_shots(terms=bound, complexity=complexity)
# 核心意图②:按复杂度分级注入数量(simple=2, medium=4, complex=6)
n = {"simple": 2, "medium": 4, "complex": 6}[complexity]
return "\n".join(f"问:{s['question']}\nSQL:{s['sql']}" for s in shots[:n])
prompt = f"{SYSTEM_PROMPT}\n\n【示例】\n{build_few_shot(user_query, 'simple')}\n\n【问题】{user_query}"
引申
System Prompt 的分层设计是"约束 LLM 搜索空间"的关键——角色定义圈定输出范围,schema 约束禁止臆造列,术语绑定把"GMV"这种业务术语硬路由到正确的指标定义,few-shot 给 LLM 看"好 SQL 长什么样"的范例。这些手段里术语绑定强路由最重要:它让"GMV"不再靠 LLM 猜,而是强行走 L2 术语→L3 规则的确定路径,这是压住幻觉的核心手段(详见 Ch 43 的幻觉分类学)。
为什么选 Git+YAML 而非数据库¶
%%{init: {'theme':'base','themeVariables':{'primaryColor':'#edf5ff','primaryTextColor':'#161616','primaryBorderColor':'#0f62fe','lineColor':'#697077','secondaryColor':'#d9fbfb','tertiaryColor':'#f2f4f8','fontSize':'14px'}}}%%
flowchart LR
subgraph Git-YAML优势["Git+YAML 的优势"]
G1[ 版本控制<br/>每次变更有 diff/历史]
G2@{ icon: "codicon:person", form: "rounded", label: PR 审查<br/>语义变更需人工 review, pos: "b", h: 36 }
G3@{ icon: "codicon:shield", form: "rounded", label: CI 校验<br/>变更前自动校验一致性, pos: "b", h: 36 }
G4@{ icon: "codicon:history", form: "rounded", label: 审计友好<br/>谁改了什么、何时改的, pos: "b", h: 36 }
end
linkStyle default stroke:#697077,stroke-width:2px
classDef bpProcess fill:#edf5ff,stroke:#0f62fe,stroke-width:2px,color:#161616
classDef bpData fill:#d9fbfb,stroke:#007d79,stroke-width:2px,color:#161616
classDef bpDecision fill:#fcf4d6,stroke:#f1c21b,stroke-width:2px,color:#161616
classDef bpSuccess fill:#defbe6,stroke:#198038,stroke-width:2px,color:#161616
classDef bpError fill:#fff1f1,stroke:#da1e28,stroke-width:2px,color:#161616
classDef bpExternal fill:#f2f4f8,stroke:#697077,stroke-width:2px,color:#161616
classDef bpInfo fill:#f6f2ff,stroke:#8a3ffc,stroke-width:2px,color:#161616
classDef bpGroup fill:#ffffff,stroke:#0f62fe,stroke-width:2px,color:#161616
class G1,G2,G3,G4 bpSuccess
图 40-4 为什么选 Git+YAML 而非数据库
| 维度 | Git+YAML | 数据库驱动 |
|---|---|---|
| 版本控制 | ✅ 原生 Git | ❌ 需自建 |
| 审查流程 | ✅ PR review | ❌ 直接改库 |
| CI 校验 | ✅ pre-commit + CI | ❌ 需额外工具 |
| 审计 | ✅ Git log | ❌ 需审计日志 |
| 查询性能 | ❌ 需加载到内存 | ✅ 原生快 |
| 适合场景 | 低频变更+高治理 | 高频变更+低治理 |
表 40-3 为什么选 Git+YAML 而非数据库
Trade-off
语义资产变更频率低(不是每次查询都改),但治理要求高(改错了 GMV 定义,影响的是所有查询)。Git+YAML 的版本控制+PR 审查+CI 校验正好对路。代价是 YAML 编写成本不低——但这本来就是"治理"的意图:让变更没那么容易随便发生。
40.3 离线发布管线¶
%%{init: {'theme':'base','themeVariables':{'primaryColor':'#edf5ff','primaryTextColor':'#161616','primaryBorderColor':'#0f62fe','lineColor':'#697077','secondaryColor':'#d9fbfb','tertiaryColor':'#f2f4f8','fontSize':'14px'}}}%%
flowchart LR
subgraph 离线发布["离线发布管线"]
YAML[Git 仓库<br/>YAML 资产] --> PRE[pre-commit 校验<br/>本地格式/语法检查]
PRE --> CI[CI 校验<br/>一致性/引用完整性]
CI --> PR[PR Review<br/>人工审查]
PR --> MERGE[合并主分支]
MERGE --> S3[发布到 S3]
S3 --> SYNC[后端 R/V/G 同步<br/>加载到检索引擎]
end
linkStyle default stroke:#697077,stroke-width:2px
classDef bpProcess fill:#edf5ff,stroke:#0f62fe,stroke-width:2px,color:#161616
classDef bpData fill:#d9fbfb,stroke:#007d79,stroke-width:2px,color:#161616
classDef bpDecision fill:#fcf4d6,stroke:#f1c21b,stroke-width:2px,color:#161616
classDef bpSuccess fill:#defbe6,stroke:#198038,stroke-width:2px,color:#161616
classDef bpError fill:#fff1f1,stroke:#da1e28,stroke-width:2px,color:#161616
classDef bpExternal fill:#f2f4f8,stroke:#697077,stroke-width:2px,color:#161616
classDef bpInfo fill:#f6f2ff,stroke:#8a3ffc,stroke-width:2px,color:#161616
classDef bpGroup fill:#ffffff,stroke:#0f62fe,stroke-width:2px,color:#161616
class YAML bpData
class PRE bpProcess
class CI bpProcess
class PR bpDecision
class MERGE bpProcess
class S3 bpData
class SYNC bpProcess
图 40-5 离线发布管线
| 阶段 | 作用 | 阻断级别 |
|---|---|---|
| pre-commit | 本地格式/语法检查 | 警告 |
| CI 校验 | 一致性(引用的表/列是否存在) | 阻断 |
| PR Review | 人工审查业务正确性 | 阻断 |
| 发布 | YAML → S3 → 检索引擎同步 | 自动 |
表 40-4 离线发布管线
CI 校验内容¶
%%{init: {'theme':'base','themeVariables':{'primaryColor':'#edf5ff','primaryTextColor':'#161616','primaryBorderColor':'#0f62fe','lineColor':'#697077','secondaryColor':'#d9fbfb','tertiaryColor':'#f2f4f8','fontSize':'14px'}}}%%
flowchart TB
subgraph CI校验["CI 校验项"]
C1[引用完整性<br/>术语引用的指标是否存在]
C2[Schema 一致性<br/>指标涉及的列是否存在]
C3[规则一致性<br/>计算规则是否自洽]
C4[命名规范<br/>命名是否遵循约定]
end
linkStyle default stroke:#697077,stroke-width:2px
classDef bpProcess fill:#edf5ff,stroke:#0f62fe,stroke-width:2px,color:#161616
classDef bpData fill:#d9fbfb,stroke:#007d79,stroke-width:2px,color:#161616
classDef bpDecision fill:#fcf4d6,stroke:#f1c21b,stroke-width:2px,color:#161616
classDef bpSuccess fill:#defbe6,stroke:#198038,stroke-width:2px,color:#161616
classDef bpError fill:#fff1f1,stroke:#da1e28,stroke-width:2px,color:#161616
classDef bpExternal fill:#f2f4f8,stroke:#697077,stroke-width:2px,color:#161616
classDef bpInfo fill:#f6f2ff,stroke:#8a3ffc,stroke-width:2px,color:#161616
classDef bpGroup fill:#ffffff,stroke:#0f62fe,stroke-width:2px,color:#161616
class C1,C2,C3,C4 bpProcess
图 40-6 CI 校验内容
CI 校验落到代码就是对所有 YAML 跑一遍引用完整性检查——术语引用的指标必须存在、指标引用的列必须在 Table 资产中:
# 示意:CI 一致性校验——引用完整性(term→metric→columns 不断链)
def validate_references(assets):
tables = {a.name: a for a in assets if a.type == "table"}
metrics = {a.name: a for a in assets if a.type == "metric"}
errors = []
for term in (a for a in assets if a.type == "term"):
bound = term.binding["asset_name"] # L2→L3:术语绑定的指标
if bound not in metrics:
errors.append(f"术语 {term.name} 绑定的指标 {bound} 不存在")
for metric in metrics.values():
if metric.base_table not in tables: # L3→L1:指标的基表必须存在
errors.append(f"指标 {metric.name} 的基表 {metric.base_table} 不存在")
table = tables[metric.base_table]
cols = {c["name"] for c in table.columns}
for col in extract_columns(metric.expression): # L3→L1:表达式引用的列必须存在
if col not in cols:
errors.append(f"指标 {metric.name} 引用了不存在的列 {col}")
if errors:
raise CiCheckError("\n".join(errors)) # 核心意图:任何断链阻断发布
引申
离线发布管线的设计灵感来自"代码发布"——语义资产像代码一样走完"写→lint→CI→review→merge→deploy"全流程。这让语义治理从"随便改"变成"受控变更",也是企业级 AI 系统跟"玩具 demo"真正拉开距离的地方。
发布管线还有两个细节值得说。增量发布:PR 合并后 build_changeset.py 基于 git diff 构建增量变更集(只含变更的资产),不搞全量重发——减少传输量和处理时间。只在首次发布或 MAJOR 变更时才走全量发布。后台自动同步:Backend 后台服务定时轮询 S3 manifest hash,检测到变更后触发 semantic_plane agent 执行 R/V/G pipeline——R 引擎 Upsert 关系表、G 引擎 MERGE AGE 图、V 引擎生成 embedding 写入 pgvector。S3 快照里不再存在的资产走软删除(标记 deleted_at,保留可追溯,不物理删除)。这实现了"业务开发只管写 YAML,其余全自动"的体验。
40.4 SemVer 版本化与认证生命周期¶
语义资产是高治理对象——每次变更都必须可追溯、可回滚。靠两个机制兜住:SemVer 版本号与认证生命周期。
这两个机制不是一开始就有 SemVer 的——最初我们只靠 Git commit hash 追踪版本。出了一次事故后才加上:有人改了 GMV 的计算规则(加了一个过滤条件),但没有 bump 版本号,导致线上检索引擎还在用旧的向量 embedding,新旧定义混着来,查出来的数据口径对不上。排查了两天才定位到是语义资产版本不一致。从那以后,CI 强制要求:有变更就必须 bump version,破坏性变更必须 MAJOR bump。
SemVer 版本化¶
每条资产有 SemVer(语义化版本)版本号,CI 强制检查变更必须 bump version:
| 版本类型 | 触发条件 | 影响范围 | 同步动作 |
|---|---|---|---|
| MAJOR | 破坏性变更(如改了指标 SQL 定义) | 影响所有依赖此资产的查询 | 触发全量重向量化 |
| MINOR | 新增字段(如给 Table 资产加了一列描述) | 增量更新,不影响已有引用 | 增量同步 |
| PATCH | 描述修正(如修正拼写、补充注释) | 不影响检索行为 | 不触发重向量化 |
表 40-8 SemVer 版本化规则
# 示意:CI 版本号递增检查(check_version_bump.py 的核心逻辑)
def check_version_bump(changed_assets, git_diff):
for asset in changed_assets:
old_ver = git_diff.get_old_version(asset.id)
new_ver = asset.version
if not is_version_bumped(old_ver, new_ver):
raise CiCheckError(f"资产 {asset.id} 有变更但未 bump version({old_ver})")
if asset.has_breaking_change() and not is_major_bump(old_ver, new_ver):
raise CiCheckError(f"资产 {asset.id} 有破坏性变更但未 MAJOR bump")
资产认证生命周期¶
资产不只是"存在"或"不存在"——它有质量状态。新资产从 draft 开始,经人工 review 后进入 certified,certified 资产在 RAG 检索中优先排序。质量差的资产会被 deprecated,不再参与检索:
%%{init: {'theme':'base','themeVariables':{'primaryColor':'#edf5ff','primaryTextColor':'#161616','primaryBorderColor':'#0f62fe','lineColor':'#697077','secondaryColor':'#d9fbfb','tertiaryColor':'#f2f4f8','fontSize':'14px'}}}%%
flowchart LR
DRAFT[draft<br/>新建,未审核] --> REVIEWED[reviewed<br/>已审核]
REVIEWED --> CERTIFIED[certified<br/>认证,优先检索]
CERTIFIED --> DEPRECATED[deprecated<br/>弃用,不参与检索]
REVIEWED -.->|质量不足| DRAFT
DEPRECATED -.->|重新启用| REVIEWED
classDef bpProcess fill:#edf5ff,stroke:#0f62fe,stroke-width:2px,color:#161616
classDef bpData fill:#d9fbfb,stroke:#007d79,stroke-width:2px,color:#161616
classDef bpSuccess fill:#defbe6,stroke:#198038,stroke-width:2px,color:#161616
classDef bpError fill:#fff1f1,stroke:#da1e28,stroke-width:2px,color:#161616
class DRAFT bpData
class REVIEWED bpProcess
class CERTIFIED bpSuccess
class DEPRECATED bpError
linkStyle default stroke:#697077,stroke-width:2px
图 40-7 资产认证生命周期
资产有 quality_score(质量分)和 lifecycle_status(生命周期状态)两个字段。Reranker(Ch 41)重排时给 certified 资产加分——用质量信号驱动检索优先级,让高质量资产优先被 LLM 看到。
认证生命周期的设计有一个取舍:我们没有选"自动认证"(比如 CI 通过就自动 certified),而是要求人工 review 后才能 certified。原因是:语义资产的"正确性"不是代码能判断的——CI 能检查引用完整性,但检查不了"GMV 不含退货订单"这个业务规则对不对。这种业务正确性只能靠懂业务的人来判断。代价是认证周期变长(一个新资产从 draft 到 certified 平均需要 2-3 天),但换来的是:certified 资产的业务正确性有保障,LLM 检索到的都是"经过人确认"的知识。
Trade-off
SemVer + 认证生命周期让语义资产的变更变得"重"——改一个描述也要 bump version,新资产要等人工 review 才能 certified。这在早期让人觉得麻烦,但随着资产规模增长到 500+ 条,这套机制的价值就体现出来了:任何一条资产的变更历史、质量状态、依赖关系都清清楚楚。如果没有这套机制,500 条资产的维护就是一场噩梦。治理的"重"是对未来维护成本的预付。
40.5 引申:对比 dbt Semantic Layer / Cube 的语义层设计¶
做语义平面之前,我花了一周时间评估 dbt Semantic Layer 和 Cube。结论是:它们是优秀的"BI 语义层",但不是"AI 语义层"。
%%{init: {'theme':'base','themeVariables':{'primaryColor':'#edf5ff','primaryTextColor':'#161616','primaryBorderColor':'#0f62fe','lineColor':'#697077','secondaryColor':'#d9fbfb','tertiaryColor':'#f2f4f8','fontSize':'14px'}}}%%
flowchart TB
subgraph 三种语义层["三种语义层方案"]
TTD@{ icon: "codicon:hubot", form: "rounded", label: NewtonData 语义平面<br/>Git+YAML+三层治理<br/>专为 Agentic BI, pos: "b", h: 36 }
DBT[dbt Semantic Layer<br/>dbt 模型延伸<br/>绑定 dbt 生态]
CUBE[Cube<br/>独立语义层服务<br/>API 优先]
end
linkStyle default stroke:#697077,stroke-width:2px
classDef bpProcess fill:#edf5ff,stroke:#0f62fe,stroke-width:2px,color:#161616
classDef bpData fill:#d9fbfb,stroke:#007d79,stroke-width:2px,color:#161616
classDef bpDecision fill:#fcf4d6,stroke:#f1c21b,stroke-width:2px,color:#161616
classDef bpSuccess fill:#defbe6,stroke:#198038,stroke-width:2px,color:#161616
classDef bpError fill:#fff1f1,stroke:#da1e28,stroke-width:2px,color:#161616
classDef bpExternal fill:#f2f4f8,stroke:#697077,stroke-width:2px,color:#161616
classDef bpInfo fill:#f6f2ff,stroke:#8a3ffc,stroke-width:2px,color:#161616
classDef bpGroup fill:#ffffff,stroke:#0f62fe,stroke-width:2px,color:#161616
class TTD bpProcess
class DBT,CUBE bpExternal
图 40-8 引申:对比 dbt Semantic Layer / Cube ...
| 维度 | NewtonData 语义平面 | dbt Semantic Layer | Cube |
|---|---|---|---|
| 管理方式 | Git + YAML | dbt YAML(同 repo) | JS/Schema 文件 |
| 治理层级 | 三层(元数据/术语/规则) | 两层(metrics/dimensions) | 两层(cubes/dimensions) |
| 发布方式 | 离线 CI + PR | dbt build | Cube Server |
| 查询接口 | R/V/G/D 四引擎 | MetricFlow API | REST/GraphQL API |
| 生态绑定 | 无(全栈自建) | dbt | 独立 |
| 适合场景 | Agentic BI 深度定制 | dbt 用户 | 通用语义层 |
表 40-7 引申:对比 dbt Semantic Layer / Cube 的语义层设计
为什么不选 dbt Semantic Layer¶
dbt Semantic Layer 最大的优势是"跟 dbt 生态无缝集成"——如果团队已经在用 dbt 做 ETL,加一个 Semantic Layer 几乎零成本。但对我们来说有三个硬伤:
第一,没有术语治理层。dbt 的 metrics 只有"维度"和"指标"两层,没有"术语→指标"的映射。用户问"GMV",dbt 不知道这对应哪个 metric——它只认 metric name,不认业务术语。在我们的场景里,业务术语和技术指标之间的映射是核心需求("阿司匹林"="乙酰水杨酸"="ASA"),dbt 做不到。
第二,绑定 dbt 生态。我们用 Glue + Redshift 做 ETL,不用 dbt。如果选 dbt Semantic Layer,要么把 ETL 迁移到 dbt(成本太高),要么只用 dbt 的 Semantic Layer 部分(跟 dbt 的 metric 定义方式强耦合,不划算)。
第三,没有"规则"层。dbt 的 metric 定义里可以写 SQL 表达式,但没有"业务规则"的概念——比如"GMV 不含退货订单"这种约束,在 dbt 里只能硬编码到 metric 的 SQL 里,没有独立的规则资产可以被 RAG 检索和注入。
为什么不选 Cube¶
Cube 比 dbt 灵活——它是独立的语义层服务,不绑定特定 ETL 工具。但同样缺少术语治理层,而且 Cube 的"cubes"定义方式更像"数据模型"而不是"业务知识"——它描述的是"这张表有哪些维度和指标",而不是"GMV 在业务上意味着什么"。
还有一个实际问题:Cube 需要部署一个独立的 Cube Server,增加了一层运维。我们的语义平面走 Git+YAML 离线发布,不需要额外的运行时服务——资产发布到 S3 后由检索引擎直接消费,架构更简单。
Trade-off
如果团队已经在用 dbt,dbt Semantic Layer 是最省心的选择——加一个 metrics 定义就行,几乎零成本。如果需要独立的语义层服务且不介意多一个运维组件,Cube 是好选择。我们选自建,是因为 dbt 生态不匹配 + 需要术语治理层(L2)——这两个需求把 dbt 和 Cube 都排除了。选型不是选"最好的",是选"最匹配约束的"。
本章小结¶
- 三层治理:L1 元数据契约(表/列/指标/join 结构,YAML 示例)→ L2 术语治理(业务术语→技术映射)→ L3 业务规则(计算规则/约束),三层相互引用、CI 校验不断链
- 治理 vs 元数据:information_schema 只描述结构,语义治理还描述业务含义——后者是 LLM 无法补全的企业特有知识(Ch 20 被动血缘→主动语义资产的演进)
- 9 种语义资产类型:表/列/指标/join/术语/规则/few-shot/上下文/权限,各有运行时消费映射(table→Engine V、join→Steiner 树建图、term→强路由...);ID 命名规范(
tbl_<domain>_<name>等)确保全局唯一 - System Prompt 分层设计:角色定义→schema 约束→安全规则→输出格式,few-shot 按复杂度分级注入,术语绑定强路由优先——降低幻觉的核心手段
- Git+YAML 选型理由:版本控制+PR 审查+CI 校验+审计——匹配"低频变更+高治理"特征(与 Ch 11 配置驱动理念一脉相承)
- 离线发布管线:pre-commit→CI 校验(引用完整性伪代码)→PR Review→增量变更集发布 S3→后台 R/V/G 自动同步(Upsert/MERGE/重向量化+软删除)——"业务开发只管写 YAML,其余全自动"
- SemVer 版本化(MAJOR/MINOR/PATCH)+ 认证生命周期(draft→reviewed→certified→deprecated + quality_score 驱动检索优先级)
- 对比 dbt/Cube:NewtonData 语义平面专为 AI Agent 设计(三层+术语+规则),dbt/Cube 主要为 BI 工具设计(两层,无术语治理层)——"BI 语义层"与"AI 语义层"的区别
下一章
Ch 41 R/V/G/D 四引擎 RAG 检索 —— 语义资产发布好了,AI 怎么检索它们?接下来看四引擎 RAG 设计。