百积木文档
开发集成

Cmodel 错误模型

规范平台服务之间的 Cmodel 响应、结构化错误和错误传导方式。

Cmodel 响应用于平台服务之间统一返回结果。现有接口已经使用 errorCodevaluedata 作为基础 envelope,Java 服务通常还会返回 systemCurrentTime。这些字段需要继续保留,避免破坏已有调用方。

基础 envelope 只能承载一层错误摘要,不适合表达跨服务调用链、上游错误、重试语义和排障线索。新的 Cmodel 标准是在旧 envelope 中增加专门的 error 字段,用它保存结构化错误信息。旧字段服务于兼容和用户展示,error 对象服务于系统内部传导、自动重试、告警归因和问题排查。

顶层 codemessage 不是基础 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_LIMITED
  • LLM_RESPONSE_PARSE_FAILED
  • CHANNEL_GATEWAY_TIMEOUT
  • MODULE_METHOD_INVALID_ARGUMENT
  • WORKFLOW_DEFINITION_NOT_FOUND
  • AUTH_TOKEN_EXPIRED

错误码必须稳定,不能直接使用上游供应商的原始错误文本。供应商原始错误放入 error.upstream

为避免微服务之间错误码重复,平台以 error.domain + error.reason 作为真正唯一的机器错误身份。error.code 是兼容人类阅读和旧调用方的短码,应由 domain/reason 映射生成。

同一个 domain 内不得重复使用同一个 reason 表示不同错误。不同 domain 可以使用相同 reason,例如多个服务都可以有 INVALID_ARGUMENT,但它们的完整身份分别是 workspace-agent.baijimu/INVALID_ARGUMENTllm-gateway.baijimu/INVALID_ARGUMENT

新增错误时需要先登记 domainreasoncodecategoryretryable 和默认 userMessage。服务实现、告警规则和调用方判断应依赖 domain/reasoncode,不得依赖自然语言文案。

错误分类

error.category 使用小写蛇形命名。平台保留分类包括:

  • validation
  • auth
  • permission
  • not_found
  • conflict
  • rate_limit
  • upstream_timeout
  • upstream_rate_limit
  • upstream_unavailable
  • internal
  • cancelled

新增分类需要先补充本文档,再进入服务实现。

传导规则

服务内部可以继续使用语言原生的错误链机制,例如 Java Throwable cause、Rust anyhow::Context、JavaScript cause。包装错误时应增加当前层语义,但不得丢失底层原因。

跨服务边界必须把错误链转换为 Cmodel error 对象。禁止只传 err.toString()getMessage() 或单层 message,因为这样会丢失原始错误码、上游状态、是否可重试和 trace 信息。

跨多个服务传递时,顶层 error 表示当前服务对外承诺的稳定错误;下游或上游错误放入 error.causes。不要在 causes 中嵌套完整 Cmodel envelope,也不要递归包装成 error.error.errorcauses 中每一项只保留原因摘要、来源服务、domain/reason/code、必要的 upstream 和 trace。

causes 按从外到内排序:第一个是最靠近当前服务的包装原因,最后一个是最底层的上游或运行时原因。调用方如果只理解顶层 error.code,也可以稳定处理;排障系统可以继续展开 causes 找到根因。

如果一次请求产生多个并列错误,例如批量校验失败,不应把它们伪装成调用链。应放入 error.details.errors 数组,每个元素都有自己的 domain/reason/code/message/target

用户展示文案和开发者排障信息必须分离。valueerror.userMessage 用于前端或渠道展示;error.messageerror.upstreamerror.causes 和日志用于排障。

调用方做重试、切换通道、告警归因时,只能依赖 error.codeerror.categoryerror.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.codeerror.retryable 和 trace。

调用方消费规则

调用方判断失败时先检查 HTTP 状态,再检查 errorCode != "0"。展示给用户时优先使用 error.userMessage,其次使用 value

调用方需要自动重试或切换通道时,只读取 error.retryableerror.categoryerror.code。排障页面、日志和告警系统应展示 tracesourceupstreamcauses

新代码不得只依赖 valueerror.message 里的自然语言文本做错误分类。

迁移要求

已有服务迁移到新 Cmodel 响应时,应保留 errorCode/value/data 基础 envelope,并在失败响应中增加结构化 error 对象。调用方随后逐步改为优先消费 error 对象。

新的平台接口和模块方法从一开始就应返回结构化 error。新增错误码、分类或调用方依赖的 details 字段,需要同步补充本文档。

本页内容