归因分析¶
每个看板都会引出同一个问题:这个指标为什么变了? 人工回答——或让 AI Agent 回答——通常意味着一轮探索式查询循环:先查两个周期的总量,再一个维度 一个维度地分组查询,然后自己对齐、比较数字,还要小心两个周期之间新出现或 消失的分段。
归因分析把这个循环压缩成一次调用。给 Dosi 一个指标、候选维度和两个日期 窗口,引擎自己跑完所有查询,返回排好序、可直接引用的分解结果:
- 哪个维度最能解释这次变化;
- 哪个分段驱动了变化、贡献了总变化的百分之多少;
- 对比率类指标,变化来自结构还是率值——比如"客单价上升,是因为已完成 订单自身客单价从 75 → 100,尽管它的订单占比在下降"。
数字由引擎按每种指标类型选用精确的方法算出,并且加得起来——每次分解都与 总量对账,结论可核对,而不是即兴拼凑。
所有接口面都可用:CLI(dosi attribute)、REST
(POST /v1/query/attribute)、MCP(attribute_metric 工具)和 Python
(Engine.attribute)。
快速开始¶
dosi --model model.yaml attribute \
--metric revenue \
--dimensions status,customers.region,products.category \
--baseline 2024-01-01..2024-02-01 \
--current 2024-02-01..2024-03-01 \
--db warehouse.duckdb
窗口是半开 ISO 区间(START..END)。--connection 指定命名数仓连接,
--where 为整个分析加过滤,默认时间路由不合适时用 --time-dimension。
REST 或 Python 的请求是同一个形状:
from dosi_engine import Engine
engine = Engine("model.yaml")
result = engine.attribute({
"metric": "revenue",
"dimensions": ["status", "customers.region"],
"baseline": {"start": "2024-01-01", "end": "2024-02-01"},
"current": {"start": "2024-02-01", "end": "2024-03-01"},
}, db_path="warehouse.duckdb")
返回什么¶
{
"metric": "avg_order_value",
"strategy": "mix_shift", // 引擎采用的分解方式
"total_change": {"baseline_value": 75.0, "current_value": 76.67,
"delta": 1.67, "pct_change": 2.22},
"dimension_ranking": [ // 排好序的根因候选
{"dimension": "status", "score": 6.0}
],
"top_dimension_values": [ // 变动最大的分段
{"dimension": "status", "value": "cancelled",
"delta": 10.0, "contribution_pct": 600.0,
"segment_kind": "entered", // 本周期新出现的分段
"drill_down": {"where_sql": "status = 'cancelled'"}},
{"dimension": "status", "value": "completed",
"delta": -8.33, "contribution_pct": -500.0,
"mix_effect": -28.69, "rate_effect": 20.35,
"baseline_rate": 75.0, "current_rate": 100.0,
"drill_down": {"where_sql": "status = 'completed'"}}
],
"factor_totals": {"mix_effect": -18.7, "rate_effect": 20.4, ...},
"warnings": [] // 结构化的注意事项,见下文
}
怎么读:
dimension_ranking按解释力排序候选维度——第一名就是"该按这个维度 切"。contribution_pct可以直接引用:"该分段解释了 X% 的变化"。超过 100% (或为负)说明分段朝相反方向变动、相互抵消——响应会明确标记这种情况。segment_kind指出分段是本周期新出现(entered)还是消失 (exited),而不是普通涨跌——这正是手工对比最容易悄悄出错的地方。- 对比率类指标,每个分段的贡献拆成结构效应(它在总体中的权重变了) 和率值效应(它自身的比率变了),并附带各分段的率值与占比——"结构 vs 表现"的叙事直接从响应里读出来,且各部分永远加总等于总变化。
per_dimension(示例中省略)携带每个维度的完整分段明细,包括该维度 的数字是否与总量对得上账。
如何帮助 Agent 推理¶
响应的设计目标,是让 Agent 用一两次调用就从"指标动了"走到可验证的根因, 全程不用自己做算术:
- 不再探查循环。 排序和贡献值都是算好、排好的,Agent 直接引用, 不用自己编排一串查询再对账。
- 每个发现自带下一步。 每个分段都带
drill_down.where_sql——可直接 粘贴的过滤条件。想继续下钻,就带上它再调一次attribute(根因递归), 或交给指标查询看该分段的走势。 - 注意事项机器可读。
warnings是一组稳定代码——分段相互抵消、高基数 维度被截断、某维度对不上账、总变化接近于零——Agent 据此知道哪些结论要 打折扣,不用解析散文。 - 拒绝也在引导,而不是报错。 引擎无法忠实分解的指标(窗口指标、去重
计数、复杂表达式)返回
strategy: "unsupported"和结构化原因,Agent 由此转向——例如窗口指标改为对其基础指标在显式窗口上归因——而不是陷入 重试循环。dosi list metrics会预先报告每个指标的attribution_strategy,调用前即可确认支持度。
指标覆盖¶
- 可加指标——求和、计数及其线性组合(含线性派生指标,另附每个成员对 变化的分解):完整维度归因。
- 比率指标——含平均值:上文的结构/率值分解。
- 暂不支持——窗口指标、去重计数和更复杂的表达式返回结构化的
unsupported响应(规划在后续阶段支持),而不是给出误导性的数字。
限制¶
- 每次调用最多 16 个候选维度,各维度独立分析;更深的下钻用
drill_down.where_sql递归。 - 每个维度的分段数有上限(默认 500,最高 1000);超过后该维度打上
truncated标记。 - 只在一个窗口出现的分段由引擎兜底处理——零填充或标记为 entered/exited—— 对比永远不会悄悄丢掉它们。