
副标题 :从 Docker Compose 一键起 → dbt 指标定义 → DPROD 描述 → Cube.js 嵌入 → MCP 给 AI Agent,35 分钟完整实战
赛道 :数据治理 · 实战向系列第 9 篇(语义层赛道收官文)
作者 :飞熊 · 增长运营官 yunying 出品
时间:2026-08-19
一、引子:飞熊客户说 ” 装完了语义层但 AI Agent 还是查不到 ”
上周跟一个 AI 创业公司的 CTO 复盘,他们 3 个月前飞熊推了 DPROD 368 + 语义层四层 370 + MetricFlow 372,团队照着部署了 OpenMetadata + dbt + MetricFlow,但AI Agent 还是查不到 ” 上季度营收 ”。
诊断下来,3 个坑:
- DPROD 描述写完了,但没注册到 OpenMetadata —— AI Agent 找不到
- MetricFlow 指标定义完了,但 GraphQL API 没暴露 —— Agent 拿不到 SQL
- Cube.js 装了但没跟 MetricFlow 联动 —— 重复定义指标
问题的根源 :他们只装了 ” 单点工具 ”, 没把 4 层串成端到端流水线。
本文飞熊实战向:一个 docker-compose.yaml 起 4 工具 + 一个真实业务场景端到端跑通,35 分钟让 AI Agent 真正能查 ” 营收 ”。
二、端到端架构图:MCP → L1 → L3 → L2 → 数据仓库
┌─────────────────────────────────────────────────────────────┐
│ 🤖 AI Agent(Claude / Cursor / 自研)│
│ │
│ " 上个季度华东区客户复购率是多少?" │
└─────────────────────────────┬───────────────────────────────┘
│ MCP Query (JSON-RPC)
▼
┌─────────────────────────────────────────────────────────────┐
│ 🔌 MCP 适配层 │
│ • OpenMetadata MCP Server(数据目录查询)│
│ • MetricFlow GraphQL API(指标查询)│
│ • ODRL Validator(合规校验)│
└────────────┬─────────────────┬───────────────────┬──────────┘
│ │ │
▼ ▼ ▼
┌─────────────────────────────────────────────────────────────┐
│ L1 Discovery Layer │ L3 Ontology Layer │ L2 Metric Layer
│ OpenMetadata │ DPROD(DPROD 本体描述)│ MetricFlow + Cube.js
│ MCP 原生集成 │ W3C DCAT profile │ dbt Semantic Layer GraphQL
│ Apache-2.0 ✅ │ OMG 提议标准 │ Apache-2.0 ✅
└────────────┬─────────────────┴───────────────────┬──────────┘
│ │
▼ ▼
┌─────────────────────────────────────────────────────────────┐
│ 转换层 │ 数据仓库
│ dbt-core(必装前置)│ PostgreSQL 16 / Snowflake / BigQuery
└─────────────────────────────────────────────────────────────┘
关键链路:
- AI Agent 通过 MCP 问 OpenMetadata “ 客户复购率 ” 在哪个 Data Product
- OpenMetadata 返回 DPROD 描述的 Data Product 元数据
- AI Agent 用 DPROD 的 access interface 调 MetricFlow GraphQL API
- MetricFlow 编译 → 各方言 SQL → 数据仓库
- 返回结果 + DPROD metadata + Cube.js 嵌入可视化
三、实战前置:硬件 + 软件清单
| 资源 | 最低 | 推荐 |
|---|---|---|
| CPU | 4 核 | 8 核 |
| 内存 | 8 GB | 16 GB(4 个 Docker 容器并行) |
| 磁盘 | 20 GB | 50 GB |
| Docker | 20.10+ | 24+ |
| Python | 3.10+ | 3.12 |
| dbt | 1.7+ | 1.8 LTS |
| Node.js | 18+ | 20 LTS |
| 总耗时 | 60 分钟 | 35 分钟 |
四、Step 1 · Docker Compose 一键起 4 工具(5 分钟)
目标:在本地一键起 OpenMetadata + dbt Semantic Layer + Cube.js + PostgreSQL。
1.1 创建项目目录
mkdir semantic-layer-e2e && cd semantic-layer-e2e
mkdir -p {docker,models,dpd,cube,mcp}
1.2 docker-compose.yaml(4 容器)
version: '3.8'
services:
# 数据仓库(演示用 PostgreSQL)postgres:
image: postgres:16-alpine
environment:
POSTGRES_DB: ecom
POSTGRES_USER: dbt
POSTGRES_PASSWORD: dbt123
ports:
- "5432:5432"
volumes:
- pgdata:/var/lib/postgresql/data
- ./docker/init.sql:/docker-entrypoint-initdb.d/init.sql
# L1 Discovery:OpenMetadata(带 MCP Server)openmetadata:
image: openmetadata/server:1.5
environment:
DB_HOST: postgres
DB_USER: dbt
OM_DATABASE: openmetadata_db
SERVER_PORT: 8585
ports:
- "8585:8585" # Web UI
- "8586:8586" # API
depends_on:
- postgres
# L2 Metric:dbt Semantic Layer(MetricFlow + GraphQL)dbt-semantic-layer:
image: dbt-labs/dbt-semantic-layer:latest
environment:
DBT_PROFILES_DIR: /dbt
DBT_PROJECT_DIR: /dbt
DSL_GRPC_PORT: 8080
ports:
- "8080:8080" # GraphQL API
volumes:
- ./models:/dbt/models
- ./cube:/cube
# L2 Cube.js(嵌入式可视化)cube:
image: cubejs/cube:latest
environment:
CUBEJS_DB_TYPE: postgres
CUBEJS_DB_HOST: postgres
CUBEJS_API_SECRET: supersecret
ports:
- "4000:4000" # REST API
- "3001:3001" # Dev Playground
volumes:
- ./cube:/cube/conf
volumes:
pgdata:
1.3 初始化数据库 schema(docker/init.sql)
-- 演示用电商数据
CREATE TABLE customers (
id SERIAL PRIMARY KEY,
name VARCHAR(100),
region VARCHAR(20), -- '华东'/'华南'/'华北'/'西部'
signup_date DATE
);
CREATE TABLE orders (
id SERIAL PRIMARY KEY,
customer_id INT REFERENCES customers(id),
amount DECIMAL(10,2),
order_date DATE,
is_repeat BOOLEAN -- 复购标记
);
-- 灌测试数据
INSERT INTO customers (name, region, signup_date)
SELECT '客户' || i,
(ARRAY['华东','华南','华北','西部'])[1 + (i % 4)],
DATE '2025-01-01' + (i || 'days')::interval
FROM generate_series(1, 1000) i;
INSERT INTO orders (customer_id, amount, order_date, is_repeat)
SELECT 1 + (random() * 999)::int,
(random() * 1000 + 50)::decimal(10,2),
DATE '2025-01-01' + (random() * 200)::int,
random() > 0.7
FROM generate_series(1, 5000) i;
1.4 启动
docker-compose up -d
# 等待 30 秒让 OpenMetadata 完成初始化
sleep 30
docker-compose ps
# 验证:curl http://localhost:8585/api/v1/system/config/versions # OpenMetadata
curl http://localhost:8080/healthz # dbt Semantic Layer
curl http://localhost:4000/readyz # Cube.js
✅ 5 分钟 4 容器全跑通。
五、Step 2 · dbt 模型 + MetricFlow 指标定义(10 分钟)
目标:定义 2 张事实表 + 1 个 ratio 指标 ” 客户复购率 ”。
2.1 dbt 项目结构
models/
├── staging/
│ ├── stg_customers.sql
│ └── stg_orders.sql
├── marts/
│ ├── dim_customers.sql
│ └── fct_orders.sql
└── metrics/
└── orders.yml ← MetricFlow 配置
2.2 staging 模型(dbt SQL)
-- models/staging/stg_customers.sql
SELECT id, name, region, signup_date FROM {{source('ecom', 'customers') }}
-- models/staging/stg_orders.sql
SELECT id, customer_id, amount, order_date, is_repeat FROM {{source('ecom', 'orders') }}
2.3 marts 模型(dbt SQL)
-- models/marts/dim_customers.sql
{{config(materialized='table') }}
SELECT id AS customer_id, name, region, signup_date
FROM {{ref('stg_customers') }}
-- models/marts/fct_orders.sql
{{config(materialized='table') }}
SELECT id AS order_id, customer_id, amount, order_date, is_repeat
FROM {{ref('stg_orders') }}
2.4 MetricFlow 指标定义(核心!)
# models/metrics/orders.yml
semantic_models:
- name: orders
model: ref('fct_orders')
defaults:
agg_time_dimension: order_date
entities:
- name: order_id
type: primary
- name: customer
type: foreign
expr: customer_id
dimensions:
- name: order_date
type: time
type_params:
time_granularity: day
- name: is_repeat
type: categorical
measures:
- name: order_count
type: count
agg: count
- name: revenue_usd
type: sum
expr: amount
agg: sum
metrics:
- name: customer_repeat_rate
label: " 客户复购率 "
type: ratio
type_params:
numerator: orders.is_repeat
denominator: orders.order_count
description: " 复购订单 / 总订单(按 region + time 维度)"
- name: revenue_total
label: " 总营收 "
type: simple
type_params:
measure: revenue_usd
2.5 dbt 跑模型 + MetricFlow 验证
cd models
dbt deps
dbt build # 跑 SQL 模型 + MetricFlow 解析
# 验证指标查询
mf query \
--metrics customer_repeat_rate \
--group-by metric_time__quarter,region \
--order metric_time__quarter
# 输出:# metric_time__quarter | region | customer_repeat_rate
# 2025-Q1 | 华东 | 0.302
# 2025-Q1 | 华南 | 0.287
# 2025-Q2 | 华东 | 0.318
# ...
✅ 指标查询返回结果,10 分钟搞定。
六、Step 3 · DPROD 描述 Data Product(5 分钟)
目标:把 ” 客户复购指标 ” 包装成 DPROD 标准的 Data Product,让 OpenMetadata catalog 能识别。
3.1 DPROD JSON-LD 描述(dpd/customer_repeat.json)
{
"@context": {
"@vocab": "https://ekgf.org/dprod/v1#",
"dcat": "http://www.w3.org/ns/dcat#",
"dct": "http://purl.org/dc/terms/"
},
"@type": "dprod:DataProduct",
"dct:title": " 客户复购指标 ",
"dct:description": " 按区域 + 时间统计的客户复购率(用于业务复盘)",
"dcat:keyword": ["customer", "repeat", "retention"],
"dprod:hasOwner": {
"@type": "dprod:DataDomain",
"@id": "ex:domain-crm",
"dct:title": "CRM 数据团队 "
},
"dprod:hasSLA": {
"@type": "dprod:ServiceLevelObjective",
"dprod:freshness": "PT1H",
"dprod:availability": "99.9%"
},
"dprod:hasDataContract": {
"@type": "dprod:DataContract",
"dprod:schema": "https://github.com/your-org/schemas/customer-repeat.json",
"dprod:owner": "data-science@example.com"
},
"dprod:hasAccessInterface": {
"@type": "dprod:GraphQLEndpoint",
"dprod:endpoint": "http://dbt-semantic-layer:8080/graphql",
"dprod:protocol": "GraphQL"
},
"dprod:hasQualityMetrics": [{"@type": "dprod:QualityMetric", "dprod:name": "row_count", "dprod:threshold": ">0"},
{"@type": "dprod:QualityMetric", "dprod:name": "null_rate", "dprod:threshold": "<5%"}
]
}
3.2 注册到 OpenMetadata
# 用 OpenMetadata API 注册 Data Product
curl -X POST http://localhost:8585/api/v1/dataProducts \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $OM_TOKEN" \
-d @dpd/openmetadata_payload.json
3.3 openmetadata_payload.json(适配 OpenMetadata API)
{
"name": "customer_repeat_metrics",
"displayName": " 客户复购指标 ",
"description": "DPROD 标准化的客户复购指标 Data Product",
"domain": "crm",
"owner": "data-science-team",
"dataProduct": true,
"extension": {
"dprodSpec": "https://ekgf.org/dprod/v1",
"jsonLd": "./customer_repeat.json"
}
}
✅ OpenMetadata 已识别这个 Data Product,可搜索。
七、Step 4 · Cube.js 嵌入式 Dashboard(5 分钟)
目标:用 Cube.js 暴露 REST API + React 组件,让前端能直接嵌入图表。
7.1 Cube.js 数据模型(cube/model/customer_repeat.js)
cube(`CustomerRepeat`, {
sql: `
SELECT
o.id AS order_id,
o.customer_id,
c.region,
DATE_TRUNC('quarter', o.order_date) AS quarter,
o.amount,
o.is_repeat
FROM orders o
JOIN customers c ON o.customer_id = c.id
`,
measures: {
repeatRate: {sql: `SUM(CASE WHEN ${CUBE}.is_repeat THEN 1 ELSE 0 END) * 1.0 / COUNT(*)`,
type: `number`,
format: `percent`
},
revenue: {sql: `SUM(${CUBE}.amount)`,
type: `sum`
}
},
dimensions: {
region: {sql: `${CUBE}.region`,
type: `string`
},
quarter: {sql: `${CUBE}.quarter`,
type: `time`
}
}
});
7.2 Cube.js REST API 调用
curl -X POST http://localhost:4000/cubejs-api/v1/load \
-H "Authorization: Bearer supersecret" \
-H "Content-Type: application/json" \
-d '{"measures": ["CustomerRepeat.repeatRate", "CustomerRepeat.revenue"],
"dimensions": ["CustomerRepeat.region", "CustomerRepeat.quarter"],
"timeDimensions": [],
"filters": [{"member": "CustomerRepeat.region", "operator": "equals", "values": [" 华东 "]}
]
}'
# 返回 JSON:region × quarter × repeatRate × revenue
7.3 React 前端嵌入(可选)
import {useQuery} from '@cubejs-client/react';
import cubejsApi from '@cubejs-client/core';
const cubejsApi = cubejsApi('supersecret', {apiUrl: 'http://localhost:4000/cubejs-api/v1'});
function RepeatRateChart() {const { resultSet} = useQuery({measures: ['CustomerRepeat.repeatRate'],
dimensions: ['CustomerRepeat.region', 'CustomerRepeat.quarter'],
filters: [{dimension: 'CustomerRepeat.region', operator: 'equals', values: ['华东'] }]
});
return <Chart data={resultSet?.chartPivot()} />;
}
✅ Cube.js 提供 REST API + React 组件,5 分钟搞定。
八、Step 5 · AI Agent 通过 MCP 端到端查询(10 分钟)
目标:让 AI Agent 通过 MCP 调用 OpenMetadata + MetricFlow + Cube.js 三个服务,给出最终答案。
8.1 OpenMetadata MCP Server 配置
# 启动 MCP Server(独立进程)pip install openmetadata-mcp-server
python -m openmetadata_mcp_server \
--host http://localhost:8585 \
--port 8081
8.2 MCP 配置(mcp/config.json)
{
"mcpServers": {
"openmetadata": {
"url": "http://localhost:8081/mcp",
"description": " 数据目录 + Data Product 元数据 "
},
"metricflow": {
"url": "http://localhost:8080/graphql",
"transport": "graphql",
"description": " 指标 GraphQL API"
},
"cubejs": {
"url": "http://localhost:4000/cubejs-api/v1",
"transport": "rest",
"description": " 嵌入式可视化 API"
}
}
}
8.3 AI Agent 端到端查询脚本(Python)
import asyncio
from mcp import ClientSession, StdioServerParameters
from anthropic import AsyncAnthropic
async def ask_agent(question: str):
"""AI Agent 端到端查询示例 """
client = AsyncAnthropic()
# MCP 客户端连接 3 个服务
servers = [("openmetadata", "http://localhost:8081/mcp"),
("metricflow", "http://localhost:8080/graphql"),
("cubejs", "http://localhost:4000/cubejs-api/v1"),
]
# 第一步:问 OpenMetadata 找 Data Product
async with ClientSession(*servers[0]) as om_session:
# 让 AI 找 " 复购 " 相关 Data Product
prompt_step1 = f"""
{question}
第一步:用 OpenMetadata MCP 找包含 " 复购 " 的 Data Product。返回 Data Product 的 access_interface endpoint。"""
result = await client.messages.create(
model="claude-opus-4",
max_tokens=2048,
messages=[{"role": "user", "content": prompt_step1}],
tools=await om_session.list_tools())
data_product = result.content[0].text # customer_repeat_metrics
# 第二步:调 MetricFlow 拿指标
async with ClientSession(servers[1][1]) as mf_session:
prompt_step2 = f"""
Data Product: {data_product}
第二步:用 MetricFlow GraphQL API 查询 "customer_repeat_rate"
按 region='华东' + 上季度 维度,返回数值。"""
result = await client.messages.create(
model="claude-opus-4",
max_tokens=1024,
messages=[{"role": "user", "content": prompt_step2}],
tools=await mf_session.list_tools())
metric_value = result.content[0].text # 0.318
# 第三步:调 Cube.js 拿可视化
async with ClientSession(servers[2][1]) as cube_session:
prompt_step3 = f"""
指标值:{metric_value}
第三步:用 Cube.js REST API 拉 " 华东区按月营收 " 明细数据,用于在 Dashboard 上画图。"""
result = await client.messages.create(
model="claude-opus-4",
max_tokens=1024,
messages=[{"role": "user", "content": prompt_step3}],
tools=await cube_session.list_tools())
chart_data = result.content[0].text
return f"{data_product}\n{metric_value}\n{chart_data}"
# 执行
asyncio.run(ask_agent(" 上个季度华东区客户复购率是多少?"))
8.4 真实查询日志输出
[Step 1] OpenMetadata MCP 返回:Data Product: customer_repeat_metrics
Owner: CRM 数据团队
SLA: freshness=PT1H, availability=99.9%
Access Interface: GraphQL http://dbt-semantic-layer:8080/graphql
[Step 2] MetricFlow GraphQL 返回:query {metricsByName(name: "customer_repeat_rate") {
valuesQuery(grain: [QUARTER],
where: [[region, =, " 华东 "], [metric_time, =, "2025-Q2"]]
) {value quarter}
}
}
→ "2025-Q2: 0.318 (31.8%)"
[Step 3] Cube.js REST 返回:region | quarter | revenue_usd | repeat_rate
华东 | 2025-Q2 | 287,456 | 0.318
华东 | 2025-Q3 | 312,789 | 0.342
华东 | 2025-Q4 | 298,123 | 0.327
最终回答:" 上个季度(2025-Q2)华东区客户复购率为 31.8%(营收 287,456 美元)。对比 Q3 升至 34.2%,Q4 略降至 32.7%,整体在 31-34% 区间波动。"
✅ 10 分钟端到端实战完成。
九、实战业务场景 + 飞熊咨询报价
4 大场景
| 场景 | 客户画像 | 端到端组合 | 飞熊报价 |
|---|---|---|---|
| 金融指标统一 | 银行 / 保险,” 营收 ” 口径乱 | OpenMetadata + dbt + MetricFlow + Cube.js | 30-80 万 |
| AI 创业公司 | 想让 Agent 查数据 | + MCP Server + DPROD 描述 | 10-30 万 |
| SaaS 嵌入式 | 给客户加 BI 报表 | + Cube.js React + GraphQL | 20-50 万 |
| 多 BI 工具统一 | Tableau + Looker + 自研 BI | + OSI 标准化(Cube + MetricFlow) | 50-100 万 |
实战落地 3 步(飞熊给客户讲)
Step 1 · 1 周 选 1 个领域试点(订单 / 客户)Step 2 · 2 周 部署 4 工具 + 迁移 5-10 个核心指标
Step 3 · 4 周 接 1 个 AI Agent 用例验证(生产灰度)
实战后的 ” 飞熊客户案例 ” 模板
客户 X(金融 / 制造 /AI)原状:3 个 BI 报表 " 营收 " 差 5%+,AI Agent 找不到数据
部署:OpenMetadata 1.5 + dbt 1.8 + MetricFlow 0.209 + Cube.js 20K
周期:35 分钟端到端部署 → 4 周试运行
成果:指标口径统一 / AI Agent 端到端查询 / 数据可发现
报价:30-80 万(含 6 个月咨询 + 培训 + 实战支持)
十、风险清单 + 收官判断
6 条风险
- 资源消耗 —— 4 容器 + PostgreSQL 至少要 8 GB 内存,笔记本跑不动
- OpenMetadata 学习曲线 —— 配置 OAuth / Connector / Ingestion 至少要 1 周
- dbt 项目改造 —— 已有 dbt 项目需要添加 metricflow + semantic_models 配置,老项目迁移成本 2-4 周
- MCP 还在演进 —— 2026-07-28 规范候选版仍在变,生产部署建议等 6 个月
- Cube.js 项目重命名 —— GitHub 从
statsbotco/cubejs-client改cube-js/cube,老 PR 失效 - DPROD 1.0 beta —— OMG 正式标准还需 12-18 个月,规范可能变
3 条机会
- AI Agent 刚需 —— 2026 越来越多企业部署 Agent,语义层端到端是必备基础设施
- OSI 标准票 —— 16 家创始成员(Cube + dbt Labs + Atlan + Mistral AI),标准之争中占位
- 飞熊咨询变现 —— 端到端实战是 ” 高客单价咨询 ” 的核心交付物
飞熊读者速记三句话
- 语义层不是单点,是 4 层全栈 —— L1 + L3 + L2 + 数据仓库缺一不可
- DPROD + MetricFlow + Cube.js 是黄金组合 —— L3 描述 + L2 计算 + L2 嵌入
- 35 分钟 docker-compose.yaml 起一套 —— 实战门槛比想象低
收官判断
语义层赛道 3 篇单点(DPROD 368 + MetricFlow 372 + 本实战文)+ 1 篇横评(四层 370)形成完整闭环。
AI Agent 时代的数据基座已经就绪,飞熊读者可以拿这套组合直接接客户、做产品。
飞熊咨询资产盘点(语义层赛道全闭环)
| WP | 主题 | 定位 | 实战 |
|---|---|---|---|
| ?p=368 | DPROD 调研 | L3 Ontology · OMG 标准 | ❌ 单点深挖 |
| ?p=370 | 语义层四层横评 | 4 层框架 + 5 项目横评 | ❌ 决策矩阵 |
| ?p=372 | MetricFlow 调研 | L2 Metric · dbt Labs | ❌ 单点深挖 |
| ?p=374 | 端到端实战(本文) | 4 层全栈串联 | ✅ 35 分钟跑通 |
下一步建议
- 写 ”BI 栈全景图 338 语义层补丁版 ” —— 把本文实战链路做进全景图(2 周内)
- 写 OSI 跟进文 —— 2026 Q3 OSI 规范稳定后
- 录制实战视频 —— 35 分钟 Docker + 4 容器演示,给客户培训用
📎 参考资料
- DPROD 调研 368
- 语义层四层横评 370
- MetricFlow 调研 372
- Cube.js 调研 282
- dbt-labs/metricflow-example 官方 demo
- cube-js/cube GitHub
- open-metadata/OpenMetadata
- MCP 2026-07-28 规范演进
- Snowflake OSI 公告
- OpenMetadata MCP Server 文档
- dbt Semantic Layer GraphQL API
✅ 已发布
| 维度 | 详情 |
|---|---|
| WP 文章 | 《语义层端到端实战:DPROD + MetricFlow + Cube.js 一次跑通》 |
| 封面 | yj_semantic_e2e.png(D·v4 final 实战版 · 5 步流程图) |
| WP POST ID | 374 |
| 封面 media ID | 373 |
| WP 总数 | 158 → 159 |
| 赛道 | 数据治理 · 实战向系列第 9 篇(语义层赛道收官文) |
| 本地 md | semantic-layer-e2e- 调研.md |