语义层端到端实战:DPROD + MetricFlow + Cube.js 一次跑通 —— 让 AI Agent 用 4 层语义查”营收”

49次阅读
语义层端到端实战:DPROD + MetricFlow + Cube.js 一次跑通 —— 让 AI Agent 用 4 层语义查

副标题 :从 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 个坑:

  1. DPROD 描述写完了,但没注册到 OpenMetadata —— AI Agent 找不到
  2. MetricFlow 指标定义完了,但 GraphQL API 没暴露 —— Agent 拿不到 SQL
  3. 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
└─────────────────────────────────────────────────────────────┘

关键链路

  1. AI Agent 通过 MCP 问 OpenMetadata “ 客户复购率 ” 在哪个 Data Product
  2. OpenMetadata 返回 DPROD 描述的 Data Product 元数据
  3. AI Agent 用 DPROD 的 access interface 调 MetricFlow GraphQL API
  4. MetricFlow 编译 → 各方言 SQL → 数据仓库
  5. 返回结果 + 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 条风险

  1. 资源消耗 —— 4 容器 + PostgreSQL 至少要 8 GB 内存,笔记本跑不动
  2. OpenMetadata 学习曲线 —— 配置 OAuth / Connector / Ingestion 至少要 1 周
  3. dbt 项目改造 —— 已有 dbt 项目需要添加 metricflow + semantic_models 配置,老项目迁移成本 2-4 周
  4. MCP 还在演进 —— 2026-07-28 规范候选版仍在变,生产部署建议等 6 个月
  5. Cube.js 项目重命名 —— GitHub 从 statsbotco/cubejs-clientcube-js/cube,老 PR 失效
  6. DPROD 1.0 beta —— OMG 正式标准还需 12-18 个月,规范可能变

3 条机会

  1. AI Agent 刚需 —— 2026 越来越多企业部署 Agent,语义层端到端是必备基础设施
  2. OSI 标准票 —— 16 家创始成员(Cube + dbt Labs + Atlan + Mistral AI),标准之争中占位
  3. 飞熊咨询变现 —— 端到端实战是 ” 高客单价咨询 ” 的核心交付物

飞熊读者速记三句话

  1. 语义层不是单点,是 4 层全栈 —— L1 + L3 + L2 + 数据仓库缺一不可
  2. DPROD + MetricFlow + Cube.js 是黄金组合 —— L3 描述 + L2 计算 + L2 嵌入
  3. 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 分钟跑通

下一步建议

  1. 写 ”BI 栈全景图 338 语义层补丁版 ” —— 把本文实战链路做进全景图(2 周内)
  2. 写 OSI 跟进文 —— 2026 Q3 OSI 规范稳定后
  3. 录制实战视频 —— 35 分钟 Docker + 4 容器演示,给客户培训用

📎 参考资料

  1. DPROD 调研 368
  2. 语义层四层横评 370
  3. MetricFlow 调研 372
  4. Cube.js 调研 282
  5. dbt-labs/metricflow-example 官方 demo
  6. cube-js/cube GitHub
  7. open-metadata/OpenMetadata
  8. MCP 2026-07-28 规范演进
  9. Snowflake OSI 公告
  10. OpenMetadata MCP Server 文档
  11. 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
正文完