对话补全
/v1/chat/completions 的每个字段我们怎么处理、流式要注意的四件事、工具调用与中途断开怎么计费。
httpPOST https://api.scirouter.cn/v1/chat/completions请求与响应都是 OpenAI Chat Completions 的形状。这一页只写我们怎么处理这些字段, 字段本身的语义以 OpenAI 的文档为准。
请求体
json{
"model": "bio/protein-72b",
"messages": [
{ "role": "system", "content": "你是一名结构生物学助手。" },
{ "role": "user", "content": "帮我判断这段序列的二级结构倾向:MKTAYIAKQR…" }
],
"max_tokens": 1024,
"temperature": 0.3,
"stream": false
}| 字段 | 必填 | 我们怎么处理 |
|---|---|---|
model | 是 | 模型 id,见「模型列表」。不存在或已下线 404 model_not_found,白名单外 403 model_not_allowed |
messages | 是 | 不能为空数组。原样转给上游 |
max_tokens | 否 | 必须是正整数;超过模型或套餐的单次上限直接 400,details.limit 给出上限,不会替你悄悄截短。不填时取两者中较小的那个 |
max_completion_tokens | 否 | max_tokens 的新名字,两个都给时以 max_tokens 为准 |
temperature / top_p | 否 | 原样转给上游 |
stop | 否 | 字符串或字符串数组,其他类型 400 |
stream | 否 | 见下文「流式」 |
stream_options.include_usage | 否 | 流式时在末尾多发一个只带 usage 的分片 |
tools / tool_choice | 否 | 见下文「工具调用」 |
metadata / user | 否 | 你的归因标签,不转给上游,见「可观测性」 |
| 其他字段 | 否 | 原样转给上游;厂商专有参数的写法见「多厂商差异」 |
请求体上限默认 4 MB,超过返回 413 payload_too_large。
非流式响应
json{
"id": "chatcmpl-…",
"object": "chat.completion",
"created": 1758585600,
"model": "bio/protein-72b",
"choices": [
{
"index": 0,
"message": { "role": "assistant", "content": "这段序列……" },
"finish_reason": "stop"
}
],
"usage": { "prompt_tokens": 42, "completion_tokens": 318, "total_tokens": 360 },
"scirouter": { "request_id": "req_…", "attempts": 1, "…": "…" }
}scirouter 字段与 X-SciRouter-* 响应头见「可观测性」。
finish_reason 是 length 时,回答被 max_tokens 截断了。 它看起来可能很完整——
一段结构数据、一段代码被截在中间时,解析不报错也不代表内容是全的。批处理请按它判断,而不是看内容长短。
流式
带 "stream": true,响应是 text/event-stream:
text: ping
data: {"id":"chatcmpl-…","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"role":"assistant","content":""}}]}
data: {"id":"chatcmpl-…","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"content":"这段序列"}}]}
data: {"id":"chatcmpl-…","object":"chat.completion.chunk","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}
data: {"id":"chatcmpl-…","object":"chat.completion.chunk","choices":[],"usage":{"prompt_tokens":42,"completion_tokens":318,"total_tokens":360}}
data: {"id":"chatcmpl-…","object":"chat.completion.chunk","choices":[],"scirouter":{"request_id":"req_…","attempts":1}}
data: [DONE]读流时要处理好四件事:
- 以
:开头的行是注释,跳过。 排队或上游很久没吐字时,网关每隔一段时间发一行: ping, 防止中间的代理把空闲连接掐掉。官方 SDK 会自动跳过。 choices可能是空数组。 末尾的用量分片(要了include_usage才有)和scirouter分片都是这样, 直接取choices[0]会越界。- 收到
data: [DONE]才算正常结束。 出错时流里会出现一个{"error": {...}}分片,之后直接断开、不发[DONE]; 没收到[DONE]就结束的流一律按失败处理,形状见「错误码」。 - 错误分片可能出现在第一个内容分片之前。 心跳一旦发出,HTTP 状态码就已经是 200 了—— 这之后的任何错误(例如排队太久超时)都只能以错误分片出现。只看状态码会把它们全部当成成功。
故障转移只发生在第一个字节之前:上游一旦开始吐字,中途失败不会换线路重来(那会让你收到两段拼起来的回答), 而是按上一条给你一个错误分片。
工具调用
tools / tool_choice 按 OpenAI 的格式写,原样转给上游;模型返回的 tool_calls 也原样交还给你。
带 tools 的请求只会发往支持工具调用的线路。一个模型的所有线路都不支持时,返回 503 model_unavailable,
details.reason 说明原因——这种 503 重试不会好,请换模型。
中途断开与计费
- 非流式请求失败不计费;
- 流式请求在吐出一部分之后失败,按已经产生的用量计费(上游给了用量就按它,没给就按已转发的文本估算);
- 你这边主动断开连接时,网关会同时取消上游,按断开前已产生的部分计费,结果不保存——
所以带
Idempotency-Key重试时会重新调用,见「缓存与幂等」。
相关
- 所有错误码与哪些该重试:「错误码」
- 重复请求直接命中、重试不重复扣费:「缓存与幂等」
- 每次调用打给了谁、慢在哪:「可观测性」