Cmodel 错误模型
规范平台服务之间的 Cmodel 响应、结构化错误和错误传导方式。
Cmodel 响应用于平台服务之间统一返回结果。现有接口已经使用 errorCode、value、data 作为基础 envelope,Java 服务通常还会返回 systemCurrentTime。这些字段需要继续保留,避免破坏已有调用方。
基础 envelope 只能承载一层错误摘要,不适合表达跨服务调用链、上游错误、重试语义和排障线索。新的 Cmodel 标准是在旧 envelope 中增加专门的 error 字段,用它保存结构化错误信息。旧字段服务于兼容和用户展示,error 对象服务于系统内部传导、自动重试、告警归因和问题排查。
顶层 code、message 不是基础 Cmodel 字段。个别服务可以有自己的历史扩展,但新的平台标准不依赖这些字段。
响应结构
成功响应:
{
"errorCode": "0",
"value": "成功",
"data": {},
"systemCurrentTime": 1709287200000
}失败响应必须保留基础字段,并返回结构化 error:
{
"errorCode": "LLM_UPSTREAM_RATE_LIMITED",
"value": "模型通道繁忙,请稍后重试",
"data": null,
"systemCurrentTime": 1709287200000,
"error": {
"code": "LLM_UPSTREAM_RATE_LIMITED",
"domain": "llm-gateway.baijimu",
"reason": "UPSTREAM_RATE_LIMITED",
"category": "upstream_rate_limit",
"message": "上游模型通道并发已满",
"userMessage": "模型通道繁忙,请稍后重试",
"retryable": true,
"source": {
"service": "llm-gateway",
"route": "codemirror",
"model": "gpt-5.4"
},
"upstream": {
"service": "codemirror",
"status": 200,
"event": "response.failed",
"code": "rate_limit_exceeded",
"message": "Concurrency limit exceeded for account, please retry later"
},
"trace": {
"requestId": "llm-agent-sess-xxx",
"traceId": "trace-xxx",
"sessionId": "sess-xxx",
"agentSessionId": "agent-sess-xxx"
},
"causes": [
{
"service": "agent-saas",
"code": "AGENT_RESPONSES_FAILED",
"domain": "agent-saas.baijimu",
"reason": "RESPONSES_FAILED",
"message": "Responses API returned response.failed"
},
{
"service": "llm-gateway",
"code": "LLM_UPSTREAM_RATE_LIMITED",
"domain": "llm-gateway.baijimu",
"reason": "UPSTREAM_RATE_LIMITED",
"message": "Upstream returned response.failed",
"upstream": {
"service": "codemirror",
"event": "response.failed",
"code": "rate_limit_exceeded"
}
}
]
}
}字段规范
| 字段 | 必填 | 说明 |
|---|---|---|
errorCode | 是 | 兼容字段。成功为 "0",失败为平台归一后的稳定错误码。 |
value | 是 | 兼容字段。成功为简短结果说明,失败为面向用户的错误摘要。 |
data | 是 | 成功响应的数据。失败响应为 null。 |
systemCurrentTime | 否 | 服务端当前时间戳。Java Cmodel 响应通常返回该字段。 |
error | 失败时是 | 失败响应的结构化错误对象。成功响应不返回 error。 |
error.code | 是 | 平台归一后的稳定错误码,供程序判断。 |
error.domain | 是 | 错误码命名空间,通常是服务或能力域,例如 llm-gateway.baijimu。 |
error.reason | 是 | 该命名空间内稳定的错误原因,例如 UPSTREAM_RATE_LIMITED。 |
error.category | 是 | 错误分类,供重试、告警和统计聚合。 |
error.message | 是 | 面向开发者和日志的错误说明,可以包含内部上下文。 |
error.userMessage | 否 | 面向用户展示的文案,不应暴露密钥、内部域名、堆栈或供应商细节。 |
error.retryable | 是 | 调用方是否可以在同一语义下重试。 |
error.source | 是 | 产生或归一该错误的平台服务、模块、方法、路由、模型等上下文。 |
error.upstream | 否 | 上游系统返回的状态、事件、错误码和错误摘要。 |
error.trace | 是 | 请求编号、链路编号、会话编号等排障索引。 |
error.causes | 否 | 跨服务原因链摘要,保留包装关系。 |
error.details | 否 | 结构化补充信息,只放可被调用方稳定消费的字段。 |
错误码
error.code 使用大写下划线命名,并带能力域前缀:
LLM_UPSTREAM_RATE_LIMITEDLLM_RESPONSE_PARSE_FAILEDCHANNEL_GATEWAY_TIMEOUTMODULE_METHOD_INVALID_ARGUMENTWORKFLOW_DEFINITION_NOT_FOUNDAUTH_TOKEN_EXPIRED
错误码必须稳定,不能直接使用上游供应商的原始错误文本。供应商原始错误放入 error.upstream。
为避免微服务之间错误码重复,平台以 error.domain + error.reason 作为真正唯一的机器错误身份。error.code 是兼容人类阅读和旧调用方的短码,应由 domain/reason 映射生成。
同一个 domain 内不得重复使用同一个 reason 表示不同错误。不同 domain 可以使用相同 reason,例如多个服务都可以有 INVALID_ARGUMENT,但它们的完整身份分别是 workspace-agent.baijimu/INVALID_ARGUMENT 和 llm-gateway.baijimu/INVALID_ARGUMENT。
新增错误时需要先登记 domain、reason、code、category、retryable 和默认 userMessage。服务实现、告警规则和调用方判断应依赖 domain/reason 或 code,不得依赖自然语言文案。
错误分类
error.category 使用小写蛇形命名。平台保留分类包括:
validationauthpermissionnot_foundconflictrate_limitupstream_timeoutupstream_rate_limitupstream_unavailableinternalcancelled
新增分类需要先补充本文档,再进入服务实现。
传导规则
服务内部可以继续使用语言原生的错误链机制,例如 Java Throwable cause、Rust anyhow::Context、JavaScript cause。包装错误时应增加当前层语义,但不得丢失底层原因。
跨服务边界必须把错误链转换为 Cmodel error 对象。禁止只传 err.toString()、getMessage() 或单层 message,因为这样会丢失原始错误码、上游状态、是否可重试和 trace 信息。
跨多个服务传递时,顶层 error 表示当前服务对外承诺的稳定错误;下游或上游错误放入 error.causes。不要在 causes 中嵌套完整 Cmodel envelope,也不要递归包装成 error.error.error。causes 中每一项只保留原因摘要、来源服务、domain/reason/code、必要的 upstream 和 trace。
causes 按从外到内排序:第一个是最靠近当前服务的包装原因,最后一个是最底层的上游或运行时原因。调用方如果只理解顶层 error.code,也可以稳定处理;排障系统可以继续展开 causes 找到根因。
如果一次请求产生多个并列错误,例如批量校验失败,不应把它们伪装成调用链。应放入 error.details.errors 数组,每个元素都有自己的 domain/reason/code/message/target。
用户展示文案和开发者排障信息必须分离。value 和 error.userMessage 用于前端或渠道展示;error.message、error.upstream、error.causes 和日志用于排障。
调用方做重试、切换通道、告警归因时,只能依赖 error.code、error.category、error.retryable 和结构化字段,不能依赖 value 的字符串匹配。
HTTP 状态
服务间接口应优先使用 HTTP 状态表达传输层和协议层失败,例如鉴权失败、参数错误、上游超时和服务不可用。业务失败可以保持 HTTP 200 加 errorCode != "0"。
无论 HTTP 状态是否为 200,响应体中的错误结构都应保持一致。调用方应同时检查 HTTP 状态和 Cmodel errorCode。
流式错误
SSE、WebSocket 和其他流式协议在响应头发出后可能无法再修改 HTTP 状态。流内失败必须转换为结构化错误事件,不能只当作普通文本或解析异常处理。
例如 Responses API 的 response.failed 事件应归一为平台错误:
{
"type": "error",
"error": {
"code": "LLM_UPSTREAM_RATE_LIMITED",
"domain": "llm-gateway.baijimu",
"reason": "UPSTREAM_RATE_LIMITED",
"category": "upstream_rate_limit",
"message": "上游模型通道并发已满",
"userMessage": "模型通道繁忙,请稍后重试",
"retryable": true,
"upstream": {
"event": "response.failed",
"code": "rate_limit_exceeded",
"message": "Concurrency limit exceeded for account, please retry later"
},
"trace": {
"requestId": "llm-agent-sess-xxx"
}
}
}如果网关已经将上游流透传给下游,负责解析流事件的服务仍必须把流内失败映射为结构化 error,并向渠道侧传递 error.code、error.retryable 和 trace。
调用方消费规则
调用方判断失败时先检查 HTTP 状态,再检查 errorCode != "0"。展示给用户时优先使用 error.userMessage,其次使用 value。
调用方需要自动重试或切换通道时,只读取 error.retryable、error.category 和 error.code。排障页面、日志和告警系统应展示 trace、source、upstream 和 causes。
新代码不得只依赖 value 或 error.message 里的自然语言文本做错误分类。
迁移要求
已有服务迁移到新 Cmodel 响应时,应保留 errorCode/value/data 基础 envelope,并在失败响应中增加结构化 error 对象。调用方随后逐步改为优先消费 error 对象。
新的平台接口和模块方法从一开始就应返回结构化 error。新增错误码、分类或调用方依赖的 details 字段,需要同步补充本文档。