Apache Ossie 是什么?为什么用 Dosi?¶
Apache Ossie(原名 Open Semantic Interchange,简称 OSI)是一个开放规范,用于在 BI 工具、AI Agent 和数据平台之间交换语义模型、指标、维度和业务上下文。
一份 Ossie 模型就是一个普通 YAML 文件。指标在里面定义一次,所有支持 Ossie 的工具 读的都是这同一份定义,不再各自重写。Dosi 是首个 Apache Ossie 原生的语义层引擎:加载 Ossie 模型,按上游规范校验,再把指标请求编译成 16 种数仓方言各自正确的 SQL。
Apache Ossie 速览¶
| 是什么 | 开放、厂商中立的语义模型规范 |
| 原名 | Open Semantic Interchange(OSI) |
| 格式 | YAML(或 JSON) |
| 描述内容 | 数据集、关系、字段与维度、指标,以及各对象上的 AI 上下文 |
| 交给引擎的部分 | 怎样把指标变成某个数仓上正确的 SQL |
| 上游 | ossie.apache.org 与 apache/ossie:核心规范、参考校验器、格式转换器 |
| 本站的引擎 | Dosi:校验、编译并执行 Ossie 模型 |
问题:人人都在重新定义"营收"¶
从头写一句"8 月营收"的 SQL,至少有三处会出错:JOIN(营收在哪张表,连上
以后会不会扇出、把总数悄悄翻倍)、维度(按下单日期还是发货日期,算毛额
还是净额,日粒度还是月粒度)、表达式(是 SUM(amount),还是从明细表算
SUM(unit_price * quantity)——却漏了折扣列,还是要用
SUM(CASE WHEN status != 'refunded' THEN amount ELSE 0 END) 才能扣掉退款)。
任何一处出错,算出来的数字看着照样像那么回事。
这正是 BI 看板、财务报表、手写查询算出三个不同"营收"的原因:一处过滤了 已取消订单,另一处没过滤,第三处 JOIN 了一张表、把总额悄悄翻倍。AI Agent 拿着裸表名写查询,也会犯一模一样的三种错误,只是更快、更有把握。结果大家 都熟悉:两个都"正确"却对不上的数字,外加一场"该信哪个"的会。
语义模型的解法是把每个指标只定义一次:revenue 是什么意思、来自哪张表、
表与表如何关联,作为唯一事实来源。之后所有查询都从这份定义派生,不再各写各的。
Apache Ossie 就是把这份模型写下来的一种开放、标准的方式。
Apache Ossie 模型长什么样¶
模型里列出数据集(表)及其字段、表与表之间的关系,以及用普通 SQL 表达式 写成的指标。下面这份是完整、合法的模型:
version: "0.2.0.dev0"
semantic_model:
- name: orders_model
description: Orders, the customers who placed them, and revenue
datasets:
- name: orders
source: main.orders
primary_key: [order_id]
fields:
- name: order_id
expression:
dialects:
- dialect: ANSI_SQL
expression: order_id
- name: customer_id
expression:
dialects:
- dialect: ANSI_SQL
expression: customer_id
- name: order_date
expression:
dialects:
- dialect: ANSI_SQL
expression: order_date
dimension:
is_time: true
- name: amount
expression:
dialects:
- dialect: ANSI_SQL
expression: amount
- name: customers
source: main.customers
primary_key: [customer_id]
fields:
- name: customer_id
expression:
dialects:
- dialect: ANSI_SQL
expression: customer_id
- name: region
expression:
dialects:
- dialect: ANSI_SQL
expression: region
relationships:
- name: orders_to_customers
from: orders
to: customers
from_columns: [customer_id]
to_columns: [customer_id]
metrics:
- name: revenue
description: Total order amount
expression:
dialects:
- dialect: ANSI_SQL
expression: SUM(orders.amount)
ai_context:
synonyms: [sales, order revenue]
- 数据集与字段:对应物理表,以及可以分组、过滤的列;
dimension: {is_time: true}标记时间维度。 - 关系:说明表怎么 JOIN,消费方不用再猜关联键。
- 指标:基于这些字段的 SQL 聚合表达式,只写一次。
ai_context:业务上下文(同义词、说明、示例),给读模型的 AI Agent 用。
Ossie 描述的是指标是什么。至于怎么把它们变成某个数仓的正确 SQL,规范刻意不管。 这正是 Dosi 要补的那块。
是交换标准,不只是一种格式¶
原名里的 Interchange(交换)依然是关键:上游 Apache Ossie 项目维护着 Ossie 与其他 语义层格式之间的双向转换器 —— dbt、Snowflake、Databricks、Salesforce/Tableau、GoodData、Honeydew、Omni 等。一个中立的枢纽,省掉格式之间两两点对点的迁移。
Dosi 直接读取标准 Ossie 模型,只要指标是 SQL 表达式(见 S-EXPR-1), 转换器的产出拿来就能用:模型今天可以从 dbt 转进来, 明天再转去 Snowflake,指标定义不会被扣在任何一家的格式里。定义一次,自由迁移。
Dosi 与 Apache Ossie 的关系¶
Apache Ossie 是契约,Dosi 是执行它的引擎。Dosi 没有自己的模型格式:输入就是 Ossie 模型;它额外提供的能力,要么发生在查询时,要么放在规范自带的扩展点里。
| Apache Ossie 定义 | Dosi 补充 |
|---|---|
| 数据集、关系、字段,以及用 SQL 表达式写的指标 | 带扇出保护的 JOIN 规划,16 种方言的 SQL |
| 参考校验器 | dosi validate --osi-basic 在自身检查之外一并运行它 |
custom_extensions,规范的厂商扩展点 |
Datus 扩展(窗口、派生指标、JOIN 类型、空值填充、时间粒度)就放在里面 |
| 每个对象上的 AI 上下文 | 原生 MCP 服务,把模型提供给 Agent,错误结构化 |
| 与其他格式互转的转换器 | 指标为 SQL 的转换结果原样作为输入 |
Dosi 有两种模式,每次调用时选择:
- 基础模式(
--osi-basic):严格的标准 Apache Ossie,模型的含义与规范完全一致; Datus 扩展忽略并给出警告。 - Datus 模式(
--osi-datus,默认):Apache Ossie 加上 Datus 扩展。
Datus 扩展都放在 custom_extensions 里,不会让模型变得不合法:其他 Ossie 工具照样
能读,不认识的扩展直接跳过。但 Datus 模式有一点比规范宽松:它还接受 POSTGRESQL
这类核心规范没有定义的厂商 SQL 方言标签。模型需要给其他 Ossie 工具使用时,请用
dosi validate --osi-basic 校验。
Dosi 由 Datus 开发,实现了 Apache Ossie 规范,但本身不是 Apache 软件基金会的项目。
Dosi 做什么¶
Dosi 是一个小巧快速的 Rust 引擎,读一份纯 Apache Ossie 模型,把一次指标请求变成对应数仓的 正确 SQL,还可以顺手执行:
flowchart LR
A["你的 Apache Ossie 模型<br/><small>指标只定义一次</small>"] --> B["Dosi"]
B --> C["正确的 SQL<br/><small>面向你的数仓</small>"]
C --> D["结果"]
给出一个指标、几个分组维度、一种数仓方言,剩下的 JOIN、聚合,以及那个方言特有的 SQL 写法,都由 Dosi 算出来。同一份模型可以为 16 种方言生成正确 SQL: DuckDB、Postgres、MySQL、ClickHouse、Snowflake、BigQuery、StarRocks、Trino、 Databricks、Oracle 等。换数仓改一个参数,指标定义不动。
用法有四种:命令行工具、REST + Arrow 服务、面向 AI Agent 的原生 MCP 服务、Python 绑定。底下是同一个引擎,给出同样的答案。
AI Agent 需要指标体系回答什么¶
当指标的消费者从看板前的人变成 AI Agent,过去靠分析师经验拿捏的问题, 指标层必须给出明确答案。每个指标有四个属性,决定了它能被怎样安全地使用:
- 可加(additive) —— 能否沿某个维度直接求和,还是一加就重复计数 (去重计数、比率)?
- 可上卷(rollable) —— 日粒度的值能否直接卷成月值,还是必须从明细重算?
- 可预测(predictable) —— 是可以外推预测的流量,还是外推毫无意义的存量?
- 无扇出(fan-out-safe) —— JOIN 会不会让行数翻倍、总数虚高?
Dosi 把这些当作引擎必须掌握的属性,而不是分析师脑子里的约定。目前它会推断 每个指标的类型(聚合型 / 比率型 / 表达式型),并且绝不悄悄重复计数: 当一个指标要 JOIN 不同明细层级的表 —— 也就是会把营收翻倍的经典扇出 —— 要么各部分在各自粒度上正确计算,要么带着结构化错误停下。 它不会把一个虚高的总数交给你。
按指标类型内置归因:Agent 问"营收为什么跌了", 一次调用即可得到贡献拆解,由引擎用与该指标代数精确匹配的算法算出 —— 可加指标走维度归因,比率指标走 LMDI 结构/率值分解 —— 推理过程可观察、 可核对,而不是幻觉。
在生成侧,datus-agent(Datus 自家的开源数据 Agent)是目前最好的一方选择:它能直接产出符合 Dosi 规范、带 Datus 扩展的 Apache Ossie YAML —— 扩展里有更高级的窗口与 派生指标,以及比基础规范更完整的 JOIN 关系检测。消费侧保持开放:任何 Agent 都能通过 MCP 服务查询,比如 Claude Code。
这些数字凭什么可信¶
除了上面的指标代数,引擎还挡住了日常制造"对不上的数字"的错误:
- 时间范围没有歧义。 2024 年 1 月 表示
[2024-01-01, 2025-01-01), 含起始、不含结束,月份边界上的天数不会被算两次。 - 错误会告诉你怎么修。 指标名写错,换来的不是一段堆栈,而是一条列出最接近选项的
提示。
--format json下每个错误都带稳定错误码和修复建议,自动化工具和 AI Agent 据此就能自我纠正。
这些保证都精确写进了一份规范性契约,见语义契约。 不读也能用 Dosi;想确切知道引擎承诺了什么,再去查也不迟。
适合谁¶
- 分析与数据工程师 —— 想要一份指标定义,在每个数仓、每个消费端都保持正确。
- 正在迁移或多数仓并存的团队 —— 不愿为每个引擎重写一遍指标 SQL。
- 以 Apache Ossie 为标准的团队 —— 需要一个直接运行现有模型的引擎,中间不夹私有格式。
- 数据应用与 AI Agent 的开发者 —— 需要一个带结构化、机器可读错误的指标 API, 而不是自由格式的 SQL。
常见问题¶
OSI 和 Apache Ossie 是一回事吗?¶
是。Open Semantic Interchange(OSI)是这个规范的旧名,Apache Ossie 是现在的名字。 写着 OSI 的模型、工具和文档,指的都是同一个规范。
Dosi 能用任意 Apache Ossie 模型吗?¶
指标用 SQL 写的模型都能用。Ossie 还允许 MDX、TABLEAU、MAQL 表达式,但 Dosi
只编译 SQL:没有任何 SQL 写法的指标会报编译错误,字段则只给警告(见
S-EXPR-1)。dosi validate --osi-basic 除了 Dosi 自己的规则,
还会用上游 Apache Ossie 校验器检查一遍。Datus 扩展是可选的。
为什么 Dosi 的一些参数还叫 osi?¶
--osi-basic、--osi-datus 以及几个环境变量是在更名之前命名的。保留原名,
现有脚本和配置才能继续用。
下一步¶
-
拿到
dosi可执行文件,几分钟内验证通过。 -
10 分钟动手走一遍,从 Apache Ossie 模型到真实结果。
-
"绝不悄悄重复计数"背后的精确规则。
-
Dosi 实现的上游规范。