决策 API

决策 API

大多数模型调用返回的是一段话,然后由你把决策从里面抠出来:提示它输出 JSON、解析、校验、格式跑偏了再重试。决策 API 去掉了这一层。你提交应用当前的状态和需要回答的问题,每个答案回来时已经是类型化的——一个类似布尔的似然值、你自己命名的某个选项,或者你自己那把刻度上的一个位置——并且附带概率。

它服务的是软件内部的判断点,而不是对话:工单分流、工具调用的放行闸、队列分级、人工介入之前的打分。

POST https://api.beatapi.io/v1/systemone

这是 TypeSafe 官方的端点路径,请求体和响应体也都照它来——照着 TypeSafe 官方 API 或它任何一个 SDK 写的客户端,只需改 base URL 和模型名,其余不用适配。POST /v1/decisions 作为别名保留。

模型适用场景
jev-1.13TypeSafe 的快速类型化决策。32K 上下文,几百毫秒返回。

这个接口不是 /v1/chat/completionsjev-1.13 在那里也不可用。请求里没有 messages,响应里也没有 choices——决策模型不生成文本。

快速开始

curl https://api.beatapi.io/v1/systemone \
-H "Authorization: Bearer $BEATAPI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "jev-1.13",
"state": "任务:季度报表前清理不活跃账号。\n待执行调用:delete_rows(table=\"customers\", where=\"last_login < 2023-01-01\")\n背景:customers 表有 48210 行,今天没有做过备份。",
"questions": {
"safe_to_run": {
"type": "noul",
"instructions": "这个操作可以不经人工批准直接执行吗?",
"criteria": {
"true": "可逆或影响很小,且明确在任务范围之内。",
"false": "破坏性、不可逆,或超出了任务需要的范围。"
}
}
}
}'
{
"id": "task_01J...",
"model": "jev-1.13",
"answers": {
"safe_to_run": { "type": "noul", "noul": 0.04 }
},
"usage": { "input_tokens": 384, "output_tokens": 22 }
}

0.04 是模型认为答案为「是」的似然——4%,所以 agent 停下来找人确认。这正是问它的意义。

请求参数

字段类型必填说明
modelstringjev-1.13
statestring | object应用此刻掌握的情况。字符串或 JSON 对象都可以——手里本来就是对象就直接发对象。
questionsobject一个或多个具名问题。名字由你定,答案就挂在这个键下面返回。

每个问题需要 type,以及视类型而定的 criteria

字段类型必填说明
typestringnoulchoicescore
instructionsstring | object | array你要问什么。像对同事说话那样写就行。choicescore 光靠 criteria 也能成立。
criteria见下视类型答案里各个取值的含义。

三种问题类型

noul——校准过的似然

用在那些值得设阈值、而不是非黑即白的是非题上。答案是 01 之间的数。

"refund_ok": {
"type": "noul",
"instructions": "这笔退款可以自动批准吗?",
"criteria": {
"true": "明确的计费错误、金额低于 100 美元、该账号首次申请。",
"false": "用量存在争议、金额超过 100 美元,或重复申请。"
}
}
"refund_ok": { "type": "noul", "noul": 0.83 }

这里 criteria 可以不填,但把两侧都说清楚会让答案更锐利。阈值由你定:高于 0.9 自动批,低于 0.6 升级处理,中间的进人工队列。

noul 就是这个类型的名字,不是 bool 写错了。类型拼错会得到一个 400,并在错误信息里列出三个合法取值。

choice——在你命名的选项里选一个

criteria 是一个对象,把每个选项名映射到它的含义。答案给出胜出的选项,并附上完整的概率分布。

"route": {
"type": "choice",
"instructions": "这张工单应该进哪个队列?",
"criteria": {
"billing": "付款、发票、退款或扣费。",
"technical": "产品坏了或在报错。",
"sales": "购买前关于套餐或价格的咨询。"
}
}
"route": {
"type": "choice",
"choice": "billing",
"probabilities": { "billing": 1, "technical": 0, "sales": 0 },
"confidence": 1
}

score——你那把刻度上的位置

criteria 是一个数组,从低到高描述刻度。答案是一个按数组下标的连续值,从 0 起算。

"urgency": {
"type": "score",
"instructions": "这张工单有多急?",
"criteria": [
"1 - 可以放一周",
"2 - 几天内回复",
"3 - 今天回复",
"4 - 一小时内回复",
"5 - 半夜也要叫人起来"
]
}
"urgency": {
"type": "score",
"score": 1.98,
"legend": { "0": "1 - 可以放一周", "1": "2 - 几天内回复",
"2": "3 - 今天回复", "3": "4 - 一小时内回复",
"4": "5 - 半夜也要叫人起来" },
"probabilities": { "0": 0, "1": 0.17, "2": 0.67, "3": 0.16, "4": 0 },
"confidence": 0.72
}

1.98 落在 legend["1"]legend["2"] 之间,明显偏向后者——「今天回复」。需要分桶就四舍五入,需要给队列排序就保留小数。

一次问多个问题

针对同一份状态,把要问的一次问完。各个答案互相独立,而状态只计费一次,不是每个问题算一遍。

curl https://api.beatapi.io/v1/systemone \
-H "Authorization: Bearer $BEATAPI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "jev-1.13",
"state": {"ticket": "本月被扣了两次款", "plan": "pro", "tenure_days": 412},
"questions": {
"refund_ok": {"type": "noul", "instructions": "可以自动批准退款吗?"},
"urgency": {"type": "score", "instructions": "有多急?", "criteria": ["低", "中", "高"]}
}
}'

响应

字段说明
model作答的模型。
answers每个问题一项,键就是你起的名字。
usage本次调用的 input_tokensoutput_tokens
idBeatAPI 请求标识,提工单时附上它。这是在厂商那三个字段之外多给的,客户端没建模就会忽略它。

响应是同步的:HTTP 成功返回就意味着决策已经做完。没有轮询,也没有产物要取。

限制

  • 上下文——statequestions 合计 32000 token。
  • 不接受采样参数。 temperaturetop_pseed 之类一律不收;这个模型在形状上本就是确定的。
  • 不支持流式。 答案是一个值,不是一串 token。
  • choice 需要非空的 criteria 对象,score 需要非空的 criteria 数组,缺了会返回 400 并指明是哪一个。instructions 可以不填,但填了空的会被挡掉而不是发给上游。

错误

HTTP 状态码code含义
400 / 422bad_requestJSON、模型、问题类型或 criteria 不合法。
401 / 403processing_unavailable认证或访问无法完成。
402insufficient_credits账户额度不足。
429rate_limit_exceeded触发速率限制。
5xxprocessing_unavailable请求未能完成。

失败的调用不消耗额度。

计费

决策只按输入 token 计费——也就是 statequestions 占用的 token——单价 $0.042 / 1M 输入 token。输出不计费,因为输出是一个类型化的值,而不是生成的文字。每次响应里的 usage.input_tokens 就是这次实际计入的量,可以逐次对账。

所以把同一份状态的多个问题合并到一次调用里,比把同一份状态发好几遍要便宜得多。