概述
CMR 是 AI 网关。CMR Decisions API 通过统一的接口和鉴权方式,为决策模型提供模型访问、请求转发、协议适配与路由等网关能力。业务方提交待评估的状态(state)以及问题集合(questions),由上游模型返回分类、评分或命题成立程度等结构化结果。CMR 本身不执行模型推理。具体判断结果、概率含义、输入限制与效果以所选上游模型为准。本文按当前仓库 LiteLLM/CMR 实现整理,内置决策协议基线为 TypeSafe Jev(System One)。调用入口为:
POST /v1/decisions
问题类型 | 填写方式 | 说明 |
分类选择 | type=choice | 从给定候选项中选择一个类别,并返回候选项概率等信息。 |
有序评分 | type=score | 按给定的有序等级评估状态,返回分数等信息。 |
命题判断 | type=noul | 评估一个命题的成立程度,通常返回 0 - 1 的数值。 |
一次请求可对同一个 state 提交多个问题,也可混合使用三种问题类型。questions 的键由业务方定义,用于将返回的答案与原问题对应。
使用场景
场景 | 推荐问题类型 | 接入方式 |
客服工单分流 | choice | 将工单作为 state,以团队标识为候选项,按 choice 分配处理团队。 |
用户意图分类 | choice | 给出清晰的意图候选项;需要承接未覆盖意图时,在 criteria 中显式定义其他类别。 |
工单紧急程度判断 | noul | 定义“需要优先处理”的含义,根据业务评测设定 noul 阈值。 |
情绪、满意度或优先级评估 | score | 使用有序 criteria 等级,结合 score、legend 和概率分布处理结果。 |
Agent 工作流条件判断 | choice / noul | 评估任务状态,决定后续处理分支或是否需要人工介入。 |
同一业务状态的多维评估 | 混合类型 | 一次提交多个带独立标识的问题,例如同时识别团队、不满程度和紧急程度。 |
业务接入时应明确候选项与等级含义,并使用自己的样本验证结果及阈值。模型返回的概率与置信度不等同于业务规则保证;执行后续业务动作的逻辑由调用方实现。
前提条件
2. 已创建模型路由实例,详细操作指导请参见 创建模型路由实例。
3. 已创建 BYOK 实例并配置上游决策模型凭证,详细操作指导请参见 创建 BYOK 实例。
4. 已创建具有目标模型访问权限的模型路由访问密钥(API Key),详细操作指导请参见 创建模型路由访问密钥。
5. 已完成决策模型关联和模型路由策略配置,详细操作指导请参见 配置模型调度管理。
6. 服务端须部署支持 Decisions 的版本,并配置好决策模型所需的协议翻译运行环境;该运行环境不可用时,接口返回 503。上述控制台操作链接沿用参考文档中的公共接入指引。
7. 本文使用
cmr-decisions 作为示例逻辑模型名称,调用前请替换为控制台实际配置的名称。上游访问凭证由服务端配置,客户端使用 CMR API Key 调用。接口地址与鉴权
请求地址
POST {CMR_BASE_URL}/v1/decisions
CMR_BASE_URL 为 CMR 实例的访问地址,请以控制台展示为准。当前处理函数注册的路径为
/v1/decisions,本文示例均使用该路径。请求头
Header | 必填 | 说明 |
Authorization | 是 | Bearer <CMR_API_KEY> |
Content-Type | 是 | 固定为 application/json |
快速开始
以下示例判断一条客服工单是否紧急。
cURL
curl --request POST "${CMR_BASE_URL}/v1/decisions" \\--header "Authorization: Bearer ${CMR_API_KEY}" \\--header "Content-Type: application/json" \\--data '{"model": "cmr-decisions","state": "我的提现已经连续失败三天,今天必须支付供应商货款,请尽快处理。","questions": {"is_urgent": {"type": "noul","instructions": "这条工单是否需要优先处理?"}}}'
Python
import osimport requestsresponse = requests.post(f"{os.environ['CMR_BASE_URL'].rstrip('/')}/v1/decisions",headers={"Authorization": f"Bearer {os.environ['CMR_API_KEY']}","Content-Type": "application/json",},json={"model": "cmr-decisions","state": "我的提现已经连续失败三天,今天必须支付供应商货款,请尽快处理。","questions": {"is_urgent": {"type": "noul","instructions": "这条工单是否需要优先处理?",}},},timeout=30,)response.raise_for_status()result = response.json()print(result["answers"]["is_urgent"]["noul"])
请求参数
基本参数
参数 | 类型 | 必填 | 说明 |
model | string | 是 | CMR 逻辑模型名称或模型别名,可用值以当前实例配置为准。 |
state | string | object | array | 是 | 需要评估的文本、结构化业务数据或上下文列表;不接受 null、数字或布尔值作为顶层 state。 |
questions | object | 是 | 问题集合;键为业务自定义的问题标识,值为问题对象。 |
请求体必须为 JSON 对象。在使用默认准入规则、鉴权通过且协议翻译运行环境可用时,缺少上述必填字段、显式传入 null,或标准字段类型错误会返回 400。校验发生在请求协议规则处理之后;管理员配置的字段转换或自定义替换规则可能改变实际进入标准校验的内容。
questions 的内部结构、问题类型、候选项数量以及 state 长度等限制由上游模型校验;网关接受顶层类型不代表上游一定接受整个请求。网关顶层校验本身不拒绝空字符串 state、空数组/对象 state 或空 questions;这些输入是否可用由上游决定。正常调用建议提交具有实际业务含义的状态与问题。问题对象
下表为 TypeSafe 基线协议的常用写法。“必填”描述问题协议的填写要求,不代表网关逐项检查这些字段。网关只要求 questions 顶层为对象,内部值不做问题对象类型、type 枚举或必填项校验;不符合上游协议的值仍可能在上游被拒绝。嵌套 JSON 结构按配置的协议规则处理,复杂结构是否有效以所选上游模型为准。
参数 | 类型 | 必填 | 说明 |
questions.<question_id> | object | 是 | 一个问题;question_id 为自定义标识,如 department 或 is_urgent。 |
questions.<question_id>.type | string | 是 | 问题类型,常用值为 choice、score 或 noul。 |
questions.<question_id>.instructions | string;也可使用上游支持的 object / array | 是 | 问题说明、判断目标与规则;简单文本最便于接入。 |
questions.<question_id>.criteria | 依问题类型而定 | 按类型填写 | choice 通常为候选标识到说明的对象;score 通常为按顺序排列的等级数组;noul 可省略。 |
分类选择(choice)
适合客服分流、意图识别、内容归类等场景。criteria 的键是候选项标识,值描述该候选项的含义;返回的 choice 通常为其中一个键。
{"model": "cmr-decisions","state": "我的提现已经连续失败三天,请帮我检查支付账户。","questions": {"department": {"type": "choice","instructions": "这条工单应该交给哪个团队处理?","criteria": {"billing": "支付、账单、退款与提现问题","technical": "程序缺陷、服务故障与系统集成问题","sales": "产品咨询、采购与合同问题"}}}}
有序评分(score)
适合满意度、情绪强度、业务优先级等有序评估。criteria 使用从低到高排列的等级数组,明确每一级的含义。等级数量和评分规则以所选上游模型为准。
{"model": "cmr-decisions","state": "我已经反馈三次,问题仍然没有解决,这让我非常失望。","questions": {"frustration": {"type": "score","instructions": "评估客户的不满程度,按 criteria 从低到高评分。","criteria": ["情绪平静", "有些不满", "非常不满"]}}}
命题判断(noul)
适合判断工单是否紧急、文本是否表达投诉意图、是否需要人工介入等。noul 通常为 0~1 的命题成立程度,而非布尔值。业务方可结合实际评测设定处理阈值。
{"model": "cmr-decisions","state": "请立刻处理,今天必须完成付款。","questions": {"is_urgent": {"type": "noul","instructions": "文本是否表达了需要立即处理的紧急诉求?"}}}
结构化状态与多个问题
state 可直接传入 JSON 对象或数组,无需先转换成字符串。对象、数组在这里表示一个待评估状态,不是批量独立请求。多个问题共享同一个 state;答案通过问题标识对应。
{"model": "cmr-decisions","state": {"ticket": {"subject": "提现失败","message": "已经失败三天,今天必须付款,我非常不满。"},"history": [{"role": "customer", "text": "昨天也反馈过,仍未解决。"}]},"questions": {"department": {"type": "choice","instructions": "选择负责处理该工单的团队。","criteria": {"billing": "支付、账单与提现问题","technical": "程序缺陷与系统故障","sales": "采购与合同咨询"}},"frustration": {"type": "score","instructions": "按从低到高的等级评估客户不满程度。","criteria": ["情绪平静", "有些不满", "非常不满"]},"is_urgent": {"type": "noul","instructions": "是否需要优先处理?"}}}
数组形式的 state 示例:
{"model": "cmr-decisions","state": [{"role": "customer", "text": "提现失败了。"},{"role": "customer", "text": "已经三天了,今天必须付款。"}],"questions": {"is_urgent": {"type": "noul", "instructions": "结合上下文,是否需要优先处理?"}}}
Provider 自定义请求参数
CMR Decisions Standard 字段为 model、state、questions。默认准入规则只保留这三个字段,未匹配规则的额外顶层字段默认被丢弃,不会自动传给上游。如需传入额外 Provider 参数,须由管理员配置对应的自定义字段准入/透传规则,或开启适用的团队自定义透传策略。参数名称、类型与含义以实际 Provider 为准,配置后应验证上游实际收到的请求。服务端还会按配置剥离凭证、内部元数据和路由控制字段,开启自定义透传不代表这些受限制字段也能作为业务参数传入。当前标准接口返回一次性 JSON 结果,没有定义 stream 流式响应参数;temperature、max_tokens 等聊天生成参数也不属于 Decisions 标准字段。当前 Decisions 端点关闭响应缓存,不应依赖相同请求命中缓存。
响应参数
基本响应字段
下表描述正常完成推理时的顶层响应。
参数 | 类型 | 是否一定返回 | 说明 |
id | string | 是 | 网关生成的请求追踪标识,格式为 dec- 加随机标识;不复用上游同名字段。 |
model | string | 是 | 协议翻译后保留的上游模型名称,可能为版本化名称;未返回有效名称时回退为逻辑模型名称,可能与请求中的 model 不同。 |
answers | object | 是 | 按问题标识组织的答案对象;通过 answers.<question_id> 对应请求中的问题。 |
usage | object | 否 | 归一化后的 Token 用量;上游未提供或翻译结果中没有 usage 时不返回。 |
当前正常响应不定义 object、created、choices 或 data 字段。发生护栏拦截等情况时,应按该实例相应的拒绝响应约定处理。
分类选择结果
参数 | 类型 | 是否一定返回 | 说明 |
answers.<question_id>.type | string | 依上游返回 | 通常为 choice。 |
answers.<question_id>.choice | string | 依上游返回 | 所选候选项标识,通常对应请求中 criteria 的一个键。 |
answers.<question_id>.probabilities | object | 依上游返回 | 候选项标识到概率的映射,概率通常在 0 - 1 之间。 |
answers.<question_id>.confidence | number | 依上游返回 | 模型返回的置信度,具体含义以模型协议为准。 |
有序评分结果
参数 | 类型 | 是否一定返回 | 说明 |
answers.<question_id>.type | string | 依上游返回 | 通常为 score。 |
answers.<question_id>.score | number | 依上游返回 | 评分结果,可能为小数;取值范围和计算口径以所选模型为准。 |
answers.<question_id>.legend | object | 否 | 等级编号到等级说明的映射,常用字符串形式的 0、1、2 等作为键。 |
answers.<question_id>.probabilities | object | 否 | 等级编号到概率的映射。 |
answers.<question_id>.confidence | number | 否 | 模型返回的置信度。 |
命题判断结果
参数 | 类型 | 是否一定返回 | 说明 |
answers.<question_id>.type | string | 依上游返回 | 通常为 noul。 |
answers.<question_id>.noul | number | 依上游返回 | 通常为 0 - 1 的命题成立程度;越大表示模型越倾向于认为命题成立。 |
TypeSafe 基线中的 noul 结果不要求 confidence 字段,不应将缺少 confidence 视为响应错误。网关要求 answers 顶层为对象,但不逐项校验、补齐或归一化答案内部字段。以上为常见结构,不代表网关保证每个答案都具备全部字段,也不代表网关已验证概率范围、概率总和或问题与答案逐一完整对应。接入方应按所选模型的协议解析结果。
用量信息
参数 | 类型 | 是否一定返回 | 说明 |
usage.prompt_tokens | integer | usage 存在时返回 | 输入 Token 数;TypeSafe 内置规则将 input_tokens 映射至此字段。 |
usage.completion_tokens | integer | usage 存在时返回 | 输出 Token 数;TypeSafe 内置规则将 output_tokens 映射至此字段。 |
usage.total_tokens | integer | usage 存在时返回 | 总 Token 数;翻译结果未提供时按输入与输出 Token 数相加。 |
用量来自协议翻译后的结果。当前实现对缺失的输入/输出计数使用 0,对负数、布尔值、非整数等无效计数也按 0 处理,因此 0 不一定表示上游实际没有消耗 Token。客户端响应不包含 usage.cost;费用口径以平台计量记录为准。
Provider 自定义响应参数
answers 内部的额外字段可随上游结果保留。响应顶层则整理为上述标准字段,上游额外返回的 id、provider 等字段不会自动透传,usage 中的额外字段也不作为通用透传契约。更换模型或 Provider 后,请重新确认答案内部结构。
响应示例
以下均为字段形式示例,数值不代表真实推理结果。模型版本名称、用量和具体答案以实际响应为准。
命题判断
{"id": "dec-7f3a2c1059814a62acb350be36a4d119","model": "typesafe/jev-1.13","answers": {"is_urgent": {"type": "noul", "noul": 0.96}},"usage": {"prompt_tokens": 120,"completion_tokens": 18,"total_tokens": 138}}
分类选择、评分与命题判断组合
对应前文“结构化状态与多个问题”的问题标识。评分示例将三个等级编号为 0、1、2,按概率加权得到 1.8;这只是示例模型的结果口径,网关不会自行计算 score。
{"id": "dec-a1b2c3d456784abca9c81234567890de","model": "typesafe/jev-1.13","answers": {"department": {"type": "choice","choice": "billing","probabilities": {"billing": 0.89, "technical": 0.10, "sales": 0.01},"confidence": 0.83},"frustration": {"type": "score","score": 1.8,"legend": {"0": "情绪平静", "1": "有些不满", "2": "非常不满"},"probabilities": {"0": 0.05, "1": 0.10, "2": 0.85},"confidence": 0.86},"is_urgent": {"type": "noul", "noul": 0.96}},"usage": {"prompt_tokens": 427,"completion_tokens": 73,"total_tokens": 500}}
未提供用量
{"id": "dec-9e8d7c6b54324d01aabbccddeeff0011","model": "typesafe/jev-1.13","answers": {"is_urgent": {"type": "noul", "noul": 0.78}}}
错误响应
当前接口不同错误阶段的响应形式不同,客户端应先按 HTTP 状态码判断失败,再兼容解析 detail 和 error。标准字段校验失败示例:缺少 state,HTTP 400。
{"detail": {"code": "invalid_decisions_request","message": "Request does not match the CMR Decisions Standard","issues": [{"path": "/state", "reason": "missing"}]}}
参数 | 说明 |
detail.code | 校验错误标识;标准请求形状错误为 invalid_decisions_request |
detail.message | 错误概要 |
detail.issues[].path | 问题字段的 JSON Pointer 路径;请求根对象使用 / |
detail.issues[].reason | 原因,如 missing、object_required 或类型校验原因 |
进入共享代理处理后的错误通常使用如下包装。以下示例表示上游返回 HTTP 529 和 {"error":"Upstream service is overloaded"} 时的代理错误结构;具体文案、type、param 和 code 以实际响应为准:
{"error": {"message": "InternalServerError: {\\"error\\":\\"Upstream service is overloaded\\"}","type": null,"param": null,"code": "529"}}
参数 | 说明 |
error.message | 错误说明,可能包含经过处理的上游诊断信息。 |
error.type | 错误类型,可能为字符串或 null,随错误来源变化;不要假设始终为固定字符串。 |
error.param | 相关参数,无明确参数时可能为 null 或其他占位值。 |
error.code | 错误码,当前常见为字符串形式的 HTTP 状态码。 |
鉴权、协议配置等依赖阶段的错误也可能使用其他 detail 结构。上游错误中的 input、ctx、value 等请求回显字段会被置为 null 后写入错误说明;错误说明也可能带异常名称前缀。不要依赖原始上游错误 JSON 被完整返回,也不要将 error.message 固定视为可直接解析的 JSON。存在附加诊断字段时,以实际响应为准。
常见 HTTP 状态码:
HTTP 状态码 | 常见场景 | 建议 |
400 | JSON 格式错误、请求体不是对象、必填字段缺失、标准字段类型错误、逻辑模型名称无效,或上游拒绝当前参数。 | 修正请求,不要盲目重试。 |
401 | 网关或上游鉴权失败,访问凭证无效或失效。 | 检查 CMR API Key 和上游凭证。 |
403 | 缺少必需的鉴权信息、鉴权头格式错误,或无权访问目标模型或接口;具体状态以鉴权配置为准。 | 检查 Authorization 和模型访问权限。 |
404 | 上游模型、资源或请求路径不存在。 | 检查上游模型及接口路径配置。 |
408 | 请求或上游调用超时。 | 结合业务时限决定是否重试。 |
422 | 上游拒绝 questions 内部结构、输入长度或其他模型约束。 | 按上游诊断修正参数;当前实现保留 422 状态码。 |
429 | 网关限流、上游配额超限或暂无可用部署。 | 结合 Retry-After(如存在)使用退避策略。 |
500 | 网关或上游内部错误,或连接上游失败。 | 记录请求标识并排查;按业务策略退避重试。 |
502 | 上游响应不是有效的预期协议结果,或协议翻译结果不满足响应契约。 | 检查上游与翻译配置,持续出现时联系支持人员。 |
503 | 协议翻译运行环境/配置不可用,或上游服务不可用。 | 检查服务与配置状态,恢复后重试。 |
504 | 上游网关超时。 | 结合业务时限决定是否重试。 |
529 | TypeSafe 上游过载。 | 当前实现保留 529,不统一改写为 500;可按临时故障退避重试。 |