跳转至

错误码

所有表面——CLI、REST、MCP、Python——都用同一套结构化错误报告失败。 错误码是稳定 API,错误文本不是:按 code(snake_case)匹配,读 candidates / suggested_retry / hint 来自纠错,不要解析文本。

错误的结构

错误沿流水线分阶段,每个阶段一个错误码枚举:

阶段 何时运行 payload 形态
load 读取模型文件 目前只有文本(无 code)
validate 模型结构检查 {severity, code, location, message}
compile 把模型降解为 IR {code, location, message, candidates?, hint?}
plan 编译指标查询 {code, message, metrics?, candidates?, suggested_retry?}
execute 在数仓上执行 SQL {code, message, hint?}

可选字段为空时省略,payload 保持紧凑。location 指出该改哪里metric 'revenue'semantic_model[0].relationships[2].to); candidates 列出错误引用的合法替代;suggested_retry / hint 给出能成功的改写。

各表面:

  • CLI —— --format json 把错误结构体以 JSON 打到 stderrvalidate --format json 在 stdout 输出 {issues, compile_errors, warnings}。退出码:0 成功,1 引擎拒绝(结构化错误),2 用法错误。已知不对称:load 错误和 非 validate 命令下的校验问题只有文本形态。
  • REST 与 MCP —— 同一个 body,外面包一层:{"error": {…}}。MCP 工具失败时把它作为 isError: true 的文本内容返回;MCP 工具参数不合法时返回的是纯文本 schema 错误。
  • Pythondosi_engine)—— ModelError / QueryError / ExecuteError,都有 .code.to_dict();planner 的 suggested_retry 到 Python 侧叫 hint
{"error": {"code": "unknown_metric",
           "message": "unknown metric \"revenu\"",
           "metrics": ["revenu"],
           "candidates": ["revenue", "order_count", "..."]}}

各命令可能出现的错误码

表面 validate issues compile 错误 plan 错误 execute 错误
CLI validate ✓(issues ✓(compile_errors
CLI query / explain / list ✓(文本,中止) query --execute
REST POST /v1/validate、MCP validate_model ✓(在 body 中) ✓(在 body 中)
REST /v1/query/compile · /explain、MCP compile_sql · explain_query —(模型启动时加载) ✓(惰性编译)
REST /v1/query/execute、MCP run_query
Python Engine(...) ModelError("invalid_model") ModelError("compile_error")
Python compile_sql / query QueryError ExecuteError

服务级错误码(bad_requestunauthorizedforbiddenbusyinternaltimeout)可伴随任意 REST/MCP 调用出现,见 服务级错误码

模型校验问题

结构检查(dosi validatePOST /v1/validate)。error 使模型无效; warning 不会——模型仍可编译。

错误码 级别 何时触发 修复
unsupported_version error version 不等于本引擎实现的 OSI 规范版本 按消息中的版本号钉住
duplicate_name error 模型、数据集、字段、指标或关系重名 重命名其一
empty_datasets error 语义模型没有数据集 至少加一个数据集
empty_columns error 关系未声明任何列对 填写 from_columns / to_columns
column_count_mismatch error from_columnsto_columns 长度不等 二者按位置配对——改成等长
unknown_dataset error 关系端点引用了未声明的数据集 修正 from: / to: 名称
unknown_column error 关系列不是所在数据集声明的字段 声明该字段或改列名
missing_sql_dialect error(指标)/ warning(字段) 缺少 ANSI_SQL(或其他 SQL)方言条目 补一条 SQL 方言表达式
self_relationship warning 关系把数据集连到自身 通常是有意的;复查即可
relationship_target_not_unique warning to_columns 不是目标的主键/唯一键——join 可能发散 让 join 指向唯一键
{"severity": "error", "code": "unknown_dataset",
 "location": "semantic_model[0].relationships[0].to",
 "message": "relationship \"orders_to_customers\" references unknown dataset \"customers\""}

编译错误

把有效模型降解为 IR(validate 信封中的 compile_errors;查询表面上在模型 首次编译时出现)。全部携带 locationcandidates / hint 见备注。

错误码 何时触发 payload 附加
unparseable_expression 表达式不是可解析的 SQL
no_sql_dialect 被使用的字段/指标没有 SQL 方言条目
unknown_dataset SUM(no_such.amount)——限定名不是数据集;另外在文档内语义模型数量 ≠ 1 时也被(误)用 candidates:数据集名
unknown_field dataset.field 中数据集没有该字段 candidates:该数据集的字段
unknown_column 裸列在任何数据集都找不到 hint:限定为 dataset.field
ambiguous_column 裸列存在于多个数据集 candidates:限定拼法
not_an_aggregate 指标表达式没有聚合 hint:包一层 SUM(...)
nested_aggregate SUM(MAX(...))
bare_column_in_metric 指标里出现未聚合的列(或纯字面量聚合) hint:对数据集字段聚合
cross_dataset_aggregate 一个聚合混用两个数据集的列 拆开聚合
count_star_needs_dataset 多数据集模型里的裸 COUNT(*) hint:改数键列
unsupported_aggregate SUM/COUNT/COUNT DISTINCT/AVG/MIN/MAX 之外的聚合
multi_column_count_distinct 已声明但目前不会产生
window_in_metric 指标表达式里的窗口函数 hint:在数据集里预计算
subquery_in_metric 指标表达式里的子查询
measure_name_collision 两个不同聚合合成了同名 measure 部分触发点带 hint
metric_reference_cycle D-DERIVE compose 指标互相引用 消息展示环路路径
derive_expression_mismatch 手写 fallback 表达式与 derive 展开不一致 hint
invalid_datus_extension DATUS payload 不合法(v 错、字段引用错等) hint 带文档锚点
unsupported_datus_ext_version payload 的 v 是更新的 major / 低于 1.0 hint 指出支持的 major
datus_ext_key_required requires: [...] 点名了本引擎未实现的键
not_implemented 声明合法但超出当前 derive/window 范围 hint 带文档锚点

编译警告

非致命:模型编译通过,但并非所有声明都生效。形态 {code, location, message};CLI 在 stderr 打 ! … 且退出码为 0。

错误码 何时触发
ignored_vendor_extension basic(标准 OSI)模式忽略了 DATUS 扩展
datus_ext_version_ahead payload 声明了更新的 minor;未知键被丢弃(消息中点名)
datus_ext_key_newer_than_declared 用了比声明的 v 更新的键,但仍被采纳
compose_no_conformed_dimensions compose 指标的成员没有共同维度——任何查询都无法分组

查询规划错误

指标查询被拒绝(dosi query/v1/query/*compile_sql / run_query)。HTTP 一律 400,例外:not_implemented501internal500

错误码 何时触发 payload 附加
empty_query 未请求任何指标 candidates:全部指标
unknown_metric 指标名不在模型里 metrics、candidates
unknown_dimension group-by 项不是维度;窗口指标的时间轴缺席 group-by 时同码 candidates suggested_retry
ambiguous_dimension 裸字段存在于多个数据集 candidates:限定拼法
grain_on_non_time_dimension 对非时间字段加 :grain suggested_retry
grain_too_fine 请求的粒度细于存储的 time_granularity suggested_retry
duplicate_output_name 两个 group-by 项产出同名输出列 suggested_retry
unknown_order_key 排序键不是输出 candidates:输出列
unsupported_filter where 里的子查询/窗口,或时间范围格式不对
aggregate_in_where where 里出现聚合(暂不支持指标过滤)
no_join_path 没有数据集能沿多对一同时到达 measure 与全部 group-by metrics
ambiguous_join_path 存在多条关系路径——语义会不同 candidates:各路径、suggested_retry
fan_out_risk 对重复敏感的聚合跨越会发散的 join metrics、suggested_retry
time_range_needs_dimension 作用域里有多个时间维度,范围目标不明 candidates
no_primary_time_dimension 用了 metric_time 但没有声明主时间维度 candidates、suggested_retry
metric_time_conflict 被查询的指标时间轴不一致 metrics、suggested_retry
unconformed_dimension 维度在派生指标的 conformed 集合之外 metrics、candidates、suggested_retry
window_reset_too_fine 窗口 reset 细于查询粒度 suggested_retry
dialect_unsupported_window_function 目标方言缺少所需窗口函数 metrics、suggested_retry
not_implemented 模型层面合法,超出当前引擎范围 metrics、suggested_retry
internal 引擎不变量被破坏——重试无用;请报 bug

典型的自纠错循环:unknown_metric → 从 candidates 里选 → 重试; fan_out_risk → 按 suggested_retry(按可达维度分组,或改成 COUNT DISTINCT/MIN/MAX):

{"code": "fan_out_risk",
 "message": "measure \"views_source_views_sum\" aggregates Sum over dataset \"views_source\", which has no many→one path to [\"bookings_source\"]; evaluating a duplicate-sensitive aggregate (SUM/AVG/COUNT) across that join would double-count rows",
 "metrics": ["views_times_booking_value"],
 "suggested_retry": "group by dimensions reachable from the measure's dataset, or restate the metric with a duplicate-insensitive aggregate (COUNT DISTINCT/MIN/MAX)"}

执行错误

在数仓上执行编译出的 SQL(query --execute/v1/query/executerun_query)。形态 {code, message, hint?}

错误码 HTTP 何时触发
config 400 连接配置有问题(URI 错、DuckDB 文件打不开、驱动选项缺失)
connection 502 数仓不可达
auth 502 凭据被拒
sql_rejected 502 数仓拒绝了 SQL(表缺失、类型错误)——消息内嵌数仓自身的报错
driver 502 连接建立后驱动层失败
timeout 504 语句或服务端执行超时

服务级错误码

dosi-server 在同一信封里追加传输层错误码, {"error": {"code", "message"}}

错误码 HTTP 何时触发
bad_request 400 JSON body 不合法、方言未知
unauthorized 401 bearer token 缺失/错误
forbidden 403 --disable-execute 下调用执行
busy 429 执行槽位占满(Retry-After: 1
timeout 504 服务端自身的执行超时
internal 500 意料之外的服务失败

Python 异常

dosi_engine wheel 抛一套异常层级(基类 OsiError,均有 .code.to_dict()):

异常 错误码 说明
ModelError ioparseinvalid_modelcompile_error invalid_modelissuescompile_errorcompile_errors 及首个错误的 candidates/hint
QueryError 上文全部 plan 错误码 suggested_retry 到达时名为 hint
ExecuteError 上文全部 execute 错误码

编译警告Engine(...) 构造时以 Python RuntimeWarning 抛出—— python -W error 可把它们升级为失败。

SQL 生成错误(parse_errorunsupported_aggregate 等)是 planner 与 渲染器之间的内部边界,只会出现在上述 compile 或 plan 错误内部, 不构成独立的用户级契约。

本页每个错误码都有测试钉住:fixtures/errors/ 的负向用例目录随每次提交 运行,实际 payload 可在 test-status 看板的 "Error contract" 区浏览。