语义层
语义层是你向 Marivo 说明数据含义的地方。你用 Python 声明
数据源、实体、维度和指标,智能体通过 语义引用(形如
sales.revenue 的限定名)来引用它们,而不是直接使用原始表名和列名。
每个对象都遵循三条规则:
- Python 声明是契约。 被装饰的函数和构建器调用是名称、定义和形状的 唯一真实来源。
- Ibis 表达式是执行语言。 装饰器请求体返回 ibis 表达式,绝不返回原始 SQL 字符串。
- SQL 文本只是元数据。 需要 SQL(用于一致性校验)时,它放在
provenance=ms.from_sql(sql=..., dialect=...)中,绝不作为可执行的请求体。
你通过两个命名空间工作:
import marivo.datasource as md # connections (md.duckdb, md.ref, ...)import marivo.semantic as ms # meaning (ms.entity, ms.metric, ...)名称如何工作
Section titled “名称如何工作”每个对象都归属于一个 领域,并通过限定引用引用:
- 领域级对象:
<domain>.<object>—— 例如sales.revenue、sales.orders。 - 实体级对象(维度和度量):
<domain>.<entity>.<field>—— 例如sales.orders.region。
一个项目是一组声明文件。数据源在 models/datasources/ 下声明一次;
语义放在 models/semantic/<domain>/ 下,每个领域有一个 _domain.py:
your-project/ marivo.toml models/ datasources/ warehouse.py # md.duckdb(name="warehouse", ...) semantic/ sales/ _domain.py # ms.domain(name="sales") + entities, metrics, ...每个语义对象都由一个 语义引用 标识——一个类型化的、不可变的句柄,
同时携带限定名和对象种类。引用在编写阶段和分析循环中是同一类型:ms.entity(...)
调用、catalog.get(...).ref 查找,以及分析意图参数,都使用同一个 SemanticRef 族。
SemanticRef 基类
Section titled “SemanticRef 基类”所有引用共享两个只读属性:
| 属性 | 类型 | 含义 |
|---|---|---|
.id | str | 限定语义 id(如 "sales.revenue")。 |
.kind | SemanticKind | 对象种类——以下八个值之一。 |
str(ref) 返回 .id,因此引用可以用在任何接受字符串 id 的地方。
相等性和哈希基于 (type, id),所以同子类同 id 的两个引用可互换。
Per-类型子类
Section titled “Per-类型子类”每个 SemanticKind 值对应一个具体的引用子类:
| 类型 | 子类 | 由谁返回 | 可调用? |
|---|---|---|---|
domain | DomainRef | ms.domain(...) | 否 |
datasource | DatasourceRef | md.ref(...) | 否 |
entity | EntityRef | ms.entity(...) | 否 |
dimension | DimensionRef | 直接列用 ms.dimension_column(...),表达式用 @ms.dimension | 是——在指标请求体中 |
time_dimension | TimeDimensionRef | 直接列用 ms.time_dimension_column(...),表达式用 @ms.time_dimension | 是——在指标请求体中 |
measure | MeasureRef | 直接列用 ms.measure_column(...),表达式用 @ms.measure | 是——在指标请求体中 |
metric | MetricRef | ms.aggregate(...)、ms.count(...)、@ms.metric、ms.ratio(...) 等 | 否 |
relationship | RelationshipRef | ms.relationship(...) | 否 |
可调用的字段引用(DimensionRef、MeasureRef、TimeDimensionRef)在指标请求体
内被调用时会解析为 ibis 表达式。其他引用如果被误调用会抛出教学性错误——它们是身份令牌,
不是装饰器。
一个族,一个访问器
Section titled “一个族,一个访问器”因为编写引用和目录引用是同一类型族,你可以将编写引用直接传给分析意图, 无需包装:
revenue = ms.aggregate(name="revenue", measure=amount, agg="sum")# revenue 是一个 MetricRef——直接传给 observe:frame = session.observe(revenue, timescope={...})目录的 catalog.get("sales.revenue").ref 返回的是同一个 MetricRef 子类。不存在
.ref.ref 链,编写和分析之间也没有类型不匹配。
工厂与归一化器
Section titled “工厂与归一化器”mv.make_ref(id, kind)—— 内部使用;已从公开接口中移除。as_ref_id(value)—— 从SemanticRef、SemanticObject或纯str中提取.id字符串。 对字符串容忍:原始 id 直接通过。
ai_context:人与智能体之间的契约
Section titled “ai_context:人与智能体之间的契约”每个语义对象和数据源都接受可选的 ai_context 参数,通过 ms.ai_context(...)
构造。业务含义和约束就放在这里——这是智能体在使用对象前会读取的上下文。所有参数都是可选的,
但未知的关键字参数会在调用时被 Python 拒绝。
| 字段 | 类型 | 必填 | 默认 | 含义 |
|---|---|---|---|---|
business_definition | str | 否 | None | 用一两句话说明对象的业务含义。 |
guardrails | list[str] | 否 | [] | 智能体必须遵守的规则:必要的过滤、排除项、范围限制。 |
synonyms | list[str] | 否 | [] | 别名,方便智能体解析自然语言引用。 |
examples | list[str] | 否 | [] | 该对象能回答的示例问题或表述。 |
instructions | str | 否 | None | 关于如何(以及如何不)使用该对象的直接指引。 |
owner_notes | str | 否 | None | 来自人类负责人的备注:来源、注意事项、已知问题。 |
ai_context=ms.ai_context( business_definition="Gross order amount before refunds.", guardrails=["Validate refund exclusions before using as net revenue."], synonyms=["sales", "gmv"], examples=["What was revenue by region last week?"],)数据源在 models/datasources/*.py 中声明,每种后端用一个类型化辅助函数。
该辅助函数注册连接。语义文件通过 md.ref("datasource.warehouse") 以类型-qualified 引用引用
数据源。
import marivo.datasource as mdimport marivo.semantic as ms
md.duckdb( name="warehouse", path="warehouse.duckdb", ai_context=ms.ai_context( business_definition="Local DuckDB warehouse for sales analysis.", guardrails=["Use only for development or approved local analysis."], ),)每个辅助函数(md.duckdb、md.mysql、md.postgres、md.trino、md.clickhouse)都
共享以下参数:
| 参数 | 类型 | 必填 | 默认 | 含义 |
|---|---|---|---|---|
name | str | 是 | — | 全局数据源名称(字母、数字、_、-)。供 md.ref(name) 使用。 |
ai_context | AiContextValue | 否 | None | 面向智能体的上下文,通过 ms.ai_context(...) 构造。 |
extra | dict | 否 | None | 类型化辅助函数未建模的、罕见的 JSON 安全 ibis 关键字参数。 |
各后端特有的参数:
| 辅助函数 | 必填 | 可选 |
|---|---|---|
md.duckdb | — | path(默认 ":memory:")、read_only(默认 False) |
md.mysql | host、database | port(3306)、autocommit、user_env、password_env |
md.postgres | host、database | port(5432)、schema、autocommit、user_env、password_env |
md.trino | host、catalog | port(8080)、schema、source、timezone、http_scheme、client_tags、session_properties、user_env、auth_env |
md.clickhouse | host | port(9000 / 9440 secure)、database、secure、settings、user_env、password_env |
import marivo.datasource as md
md.trino( name="lake", host="trino.example.internal", catalog="hive", user_env="TRINO_USER", auth_env="TRINO_AUTH",)ms.domain(...) 打开一个命名空间。每个 _domain.py 调用一次。它返回一个
DomainRef,你可以把它作为 domain= 传入,以覆盖在同级文件中声明的对象的活动领域。
| 参数 | 类型 | 必填 | 默认 | 含义 |
|---|---|---|---|---|
name | str | 是 | — | 领域命名空间,例如 "sales"。对象成为 <name>.<object>。 |
default | bool | 否 | True | 为 True 时,本文件中的 decorators 在未传 domain= 时解析到该领域。 |
ai_context | AiContextValue | 否 | None | 面向智能体的上下文,通过 ms.ai_context(...) 构造。 |
import marivo.semantic as ms
ms.domain(name="sales")实体是一个物理源(一张表或一个文件)加上它的主键。它是维度、 度量和指标依附的锚点。
| 参数 | 类型 | 必填 | 默认 | 含义 |
|---|---|---|---|---|
name | str | 是 | — | 实体名称。成为 <domain>.<name>。 |
datasource | DatasourceRef | 是 | — | md.ref("datasource.warehouse")。 |
source | 来源构建器 | 是 | — | ms.table(...)、ms.parquet(...) 或 ms.csv(...)。 |
primary_key | list[str] | 否 | None | 构成主键的列名。 |
versioning | ms.snapshot | ms.validity | 否 | None | 快照或 SCD2 有效性版本控制(见下)。 |
domain | DomainRef | 否 | 文件默认 | 覆盖活动领域。 |
ai_context | AiContextValue | 否 | None | 面向智能体的上下文,通过 ms.ai_context(...) 构造。 |
warehouse = md.ref("datasource.warehouse")
orders = ms.entity( name="orders", datasource=warehouse, source=ms.table("orders"), primary_key=["order_id"], ai_context=ms.ai_context(business_definition="One row per order."),)来源 builders
Section titled “来源 builders”| 构建器 | 必填 | 可选 | 用于 |
|---|---|---|---|
ms.table(name) | name | database | 数据源中的一张表(Trino/MySQL 用 database="schema")。 |
ms.parquet(path) | path | hive_partitioning、columns | Parquet 文件(通常经 DuckDB)。 |
ms.csv(path) | path | header、delimiter、columns | CSV 文件(通常经 DuckDB)。 |
版本化(可选)
Section titled “版本化(可选)”对于行会随时间变化的实体,声明如何读取其当前状态:
ms.snapshot(partition_field, grain="day", timezone=None, format=None)—— 按天分区的快照;读取最新分区。ms.validity(valid_from, valid_to, interval, open_end, timezone=None)—— SCD2 有效性区间。interval为"closed_open"([from, to))或"closed_closed";open_end列出表示“仍然有效”的哨兵值(例如 SQLNULL用(None,),或("9999-12-31",))。
维度是用来分组或过滤的分类属性。直接物理列使用
ms.dimension_column(...);需要表达式(例如 table.region.upper())时,
使用 @ms.dimension 装饰器,其请求体返回一个针对实体表的 ibis 表达式。
| 参数 | 类型 | 必填 | 默认 | 含义 |
|---|---|---|---|---|
name | str | 是 | — | 维度名称。成为 <domain>.<entity>.<name>。 |
entity | EntityRef | str | 是 | — | 所属实体。 |
column | str | 是 | — | 实体表上的物理列名。 |
domain | DomainRef | 否 | 文件默认 | 覆盖活动领域。 |
ai_context | AiContextValue | 否 | None | 面向智能体的上下文,通过 ms.ai_context(...) 构造。 |
region = ms.dimension_column( name="region", entity=orders, column="region", ai_context=ms.ai_context(business_definition="Sales reporting region."),)时间维度是携带粒度和解析元数据的特殊维度。只有时间维度才能作为
session.observe 的时间轴。
| 参数 | 类型 | 必填 | 默认 | 含义 |
|---|---|---|---|---|
name | str | 是 | — | 维度名称。 |
entity | EntityRef | str | 是 | — | 所属实体。 |
column | str | 是 | — | 实体表上的物理列名。 |
granularity | 粒度字面量 | 是 | — | year、quarter、month、week、day、hour、minute 或 second —— 查询有意义的最细粒度。 |
parse | 解析变体 | 否 | None | 源列如何变成时间值(见下)。原生时间列可省略——分析时自动推断解析变体。 |
is_default | bool | 否 | False | 当实体有多个时间轴时,标记默认时间轴。省略 time_dimension= 时 observe 会使用它。 |
domain | DomainRef | 否 | 文件默认 | 覆盖活动领域。 |
ai_context | AiContextValue | 否 | None | 面向智能体的上下文,通过 ms.ai_context(...) 构造。 |
parse= 值声明列的物理编码。省略时,分析时会根据列的 ibis dtype 自动推断解析变体(原生
date、datetime、timestamp 列无需显式指定解析)。对于字符串或整数列,需提供
ms.strptime(format) 或 ms.hour_prefix(prefix)。该变体必须与 granularity 兼容(例如
hour 粒度需要带时间的格式)。
| 构建器 | 源列是… | 关键参数 |
|---|---|---|
(省略 parse) | 原生时间列 | — |
ms.datetime() | 原生 datetime | timezone(IANA)、sample_interval |
ms.timestamp() | 原生 timestamp | timezone(IANA)、sample_interval |
ms.strptime(format) | 需要解析的字符串/整数 | timezone、sample_interval |
ms.hour_prefix(prefix) | 仅含小时的分区 | sample_interval —— prefix 是提供日期的 day 粒度时间-维度 id |
timezone 默认为数据源引擎时区;只有当列的挂钟含义不同时才设置它(例如 "UTC")。
形如 (5, "minute") 的 sample_interval 标记一个被周期采样的时间轴,供半可加
折叠使用。
# Day partition stored as the string "20260131"log_date = ms.time_dimension_column( name="log_date", entity=orders, column="dt", granularity="day", parse=ms.strptime("%Y%m%d"), is_default=True,)
# Native UTC timestamp, usable for sub-day bucketsevent_ts = ms.time_dimension_column( name="event_ts", entity=orders, column="event_ts", granularity="minute", parse=ms.timestamp(timezone="UTC"),)度量是你打算聚合的行级数量事实(例如金额或数量)。直接物理列使用
ms.measure_column(...);需要基于一个或多个列的表达式时使用 @ms.measure。度量
携带可加性和可选的单位。
| 参数 | 类型 | 必填 | 默认 | 含义 |
|---|---|---|---|---|
name | str | 是 | — | 度量名称。成为 <domain>.<entity>.<name>。 |
entity | EntityRef | str | 是 | — | 所属实体。 |
column | str | 是 | — | 实体表上的物理列名。 |
additivity | 可加性值 | 是 | — | "additive"、"non_additive" 或 ms.semi_additive(...)。 |
unit | str | 否 | None | UCUM 单位令牌:"USD"、"CNY"、"%"、"ms"、"{order}"。 |
domain | DomainRef | 否 | 文件默认 | 覆盖活动领域。 |
ai_context | AiContextValue | 否 | None | 面向智能体的上下文,通过 ms.ai_context(...) 构造。 |
amount = ms.measure_column( name="amount", entity=orders, column="amount", additivity="additive", unit="CNY",)指标是智能体出发所依赖的、可信的、可直接用于分析的数值。Marivo 提供几种编写 形态 —— 按数值的计算方式选择。
来自度量的简单指标 —— ms.aggregate
Section titled “来自度量的简单指标 —— ms.aggregate”聚合一个度量。无请求体;可加性从度量继承。
| 参数 | 类型 | 必填 | 默认 | 含义 |
|---|---|---|---|---|
name | str | 是 | — | 指标名称。 |
measure | MeasureRef | str | 是 | — | 要聚合的度量。 |
agg | 聚合方式 | 是 | — | "sum"、"mean"、"min"、"max" 等。 |
fold | 折叠 | 否 | None | 半可加度量的时间-折叠覆盖。 |
unit | str | 否 | 继承 | 覆盖从度量推导的单位。 |
domain / ai_context | — | 否 | — | 同其他对象。 |
revenue = ms.aggregate(name="revenue", measure=amount, agg="sum")Count 指标 —— ms.count
Section titled “Count 指标 —— ms.count”需要计实体行数时使用 ms.count,不要为了计行数额外声明冗余度量。这个辅助函数
只接受引用,并从实体引用推断指标所属领域。
order_count = ms.count(name="order_count", entity=orders, ai_context=ms.ai_context(business_definition="订单总数。"))来自 ibis 请求体的 tier-2 自定义指标 —— @ms.metric
Section titled “来自 ibis 请求体的 tier-2 自定义指标 —— @ms.metric”当指标无法用 ms.aggregate(...)、ms.count(...) 或派生指标构建器表达时,
才使用这个 escape hatch。请求体返回一个 ibis 聚合;你直接声明 additivity。
| 参数 | 类型 | 必填 | 默认 | 含义 |
|---|---|---|---|---|
name | str | 否 | 函数名 | 指标名称。 |
entities | list[EntityRef | str] | 是 | — | 请求体读取的实体。 |
additivity | 可加性值 | 是 | — | "additive"、"non_additive" 或 ms.semi_additive(...)。 |
root_entity | EntityRef | str | 否 | 单个实体 | 当 entities 多于一个时必填。 |
fanout_policy | "block" | "aggregate_then_join" | 否 | "block" | 如何处理跨实体连接的 fan-out。 |
unit | str | 否 | None | UCUM 单位令牌。 |
provenance | SqlProvenance | 否 | None | 用于一致性校验的 ms.from_sql(sql=..., dialect=...)。 |
domain / ai_context | — | 否 | — | 同其他对象。 |
@ms.metric( entities=[orders], additivity="additive", name="revenue", provenance=ms.from_sql( sql="SELECT SUM(amount) AS revenue FROM orders", dialect="duckdb", ), ai_context=ms.ai_context(business_definition="Gross order amount before refunds."),)def revenue(table): return table.amount.sum()派生指标 —— ms.ratio / ms.weighted_average / ms.linear
Section titled “派生指标 —— ms.ratio / ms.weighted_average / ms.linear”由其他指标组合而成、无请求体的指标。其计算完全来自组成成分。
| 构建器 | 必填 | 计算 |
|---|---|---|
ms.ratio(name, numerator, denominator) | 两个引用 | numerator / denominator(如客单价、各类比率) |
ms.weighted_average(name, value, weight) | 两个引用 | 加权平均;之后 attribute 拆分混合与 rate |
ms.linear(name, add, subtract) | add(共 ≥2 项) | add 之和减去 subtract(如 net = gross - refunds) |
它们也都接受 unit、domain 和 ai_context。
net_revenue = ms.linear(name="net_revenue", add=[gross_revenue], subtract=[refunds])aov = ms.ratio(name="aov", numerator=total_amount, denominator=orders_count)可加性与来源信息辅助器
Section titled “可加性与来源信息辅助器”ms.semi_additive(over, fold)—— 用于在大多数轴上可加、但需在某个时间轴上折叠的 快照/状态事实。over必须是@ms.time_dimension(...)返回的状态时间 维度引用;fold为"last"、"first"、"mean"、"max"或("percentile", 0.95)。ms.from_sql(sql, dialect)—— 把 SQL 作为仅来源信息 附加,用于启用ms.parity_check(...)。它永远不会作为指标请求体执行。
snapshot_date = ms.time_dimension_column( name="snapshot_date", entity=inventory_daily, column="snapshot_date", granularity="day",)
on_hand_units = ms.measure_column( name="on_hand_units", entity=inventory_daily, column="on_hand_units", additivity=ms.semi_additive(over=snapshot_date, fold="last"),)声明两个实体如何连接,使指标和维度能够跨越它们。键使用维度 引用,而非原始列名。
| 参数 | 类型 | 必填 | 默认 | 含义 |
|---|---|---|---|---|
name | str | 是 | — | 关系名称。 |
from_entity | EntityRef | str | 是 | — | 源实体。 |
to_entity | EntityRef | str | 是 | — | 目标实体。 |
keys | list[JoinKey] | 是 | — | 一个或多个 ms.join_on(from_key, to_key) 配对。 |
domain / ai_context | — | 否 | — | 同其他对象。 |
ms.relationship( name="orders_to_customers", from_entity=orders, to_entity=customers, keys=[ms.join_on(order_customer_id, customer_id)],)加载与就绪检查门禁
Section titled “加载与就绪检查门禁”声明就绪后,加载目录并检查它:
import marivo.semantic as ms
catalog = ms.load() # SemanticCatalogcatalog.list().show() # everything, groupedcatalog.list(kind="metric").show() # just metricsrevenue = catalog.get("sales.revenue") # one objectregion = catalog.get("sales.orders.region") # also an object在任何分析之前,检查 就绪检查 —— 把未完整声明的对象挡在分析之外的结构性闸门:
report = ms.readiness()if report.status == "blocked": report.show() # blockers, with the next step for each另外两个检查支持编写:
ms.richness()—— 建议性的覆盖度/深度报告;永不阻塞。ms.parity_check("sales.revenue")—— 用指标的provenanceSQL 运行并比对结果。 需要provenance=ms.from_sql(...)。