Responses(OpenAI Responses 格式)
/v1/responses:用新版 OpenAI SDK 的 responses.* 或 Codex CLI 接入,以及它和对话补全不一样的地方(无状态、两条走法、事件流)。
更新于 2026-10-06约 8 分钟读完
httpPOST https://api.scirouter.cn/v1/responses请求与响应是 OpenAI Responses API 的形状。新版 OpenAI SDK 的 client.responses.* 与 Codex CLI(wire_api = "responses")这类按 Responses 格式发请求的客户端,换掉 base URL 和 Key 就能用;
平台上的对话模型都能这样调,不限于 OpenAI——背后线路不是原生支持 Responses 的,网关会替你转换(见下文「请求怎么到上游」)。
接入
#OpenAI SDK 会在 base URL 后面拼 /responses,所以 base URL 就是 https://api.scirouter.cn/v1(带 /v1),与对话补全相同。Key 是 SciRouter 控制台新建的那一把(sk-sr- 开头,见认证与 API Key),OpenAI 官方的 Key 在这里 401。
Python:
pythonimport os
from openai import OpenAI
client = OpenAI(api_key=os.environ["SCIROUTER_API_KEY"], base_url="https://api.scirouter.cn/v1")
r = client.responses.create(
model="YOUR_MODEL_ID",
instructions="你是一个严谨的科研助手。",
input="这段序列的二级结构倾向?",
max_output_tokens=1024,
)
print(r.output_text)TypeScript:
tsimport OpenAI from 'openai'
const client = new OpenAI({ apiKey: process.env.SCIROUTER_API_KEY, baseURL: 'https://api.scirouter.cn/v1' })
const r = await client.responses.create({
model: 'YOUR_MODEL_ID',
input: '这段序列的二级结构倾向?',
max_output_tokens: 1024,
})
console.log(r.output_text)curl:
bashcurl https://api.scirouter.cn/v1/responses \
-H "Authorization: Bearer $SCIROUTER_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "YOUR_MODEL_ID",
"input": "这段序列的二级结构倾向?",
"max_output_tokens": 1024
}'Codex CLI
#在 ~/.codex/config.toml 里加一个指向平台的 provider,并把默认模型换成平台上的模型 id:
tomlmodel = "YOUR_MODEL_ID"
model_provider = "scirouter"
[model_providers.scirouter]
name = "SciRouter"
base_url = "https://api.scirouter.cn/v1"
env_key = "SCIROUTER_API_KEY"
wire_api = "responses"然后 export SCIROUTER_API_KEY=sk-sr-…(SciRouter 控制台创建的 Key,不是 OpenAI 的)再启动 codex。Codex 每一轮都把完整对话(含它自己带回的推理项)放进 input、store 写 false,正好是平台的无状态口径(见下文)。
它的 shell / apply_patch 工具是函数工具,所有模型都能用;只有把线路标成「原生 Responses」的模型才能把加密的推理项续接回去,其它模型会丢掉推理项(不报错,见「请求怎么到上游」)。
请求
#| 字段 | 必填 | 说明 |
|---|---|---|
model | 是 | 平台的模型 id,也可以写后台给它配的别名;响应里的 model 是解析后的平台 id |
input | 是 | 字符串,或输入项数组:message(input_text / input_image / output_text / refusal)、function_call、function_call_output、reasoning(客户端带回的,见下文)。不能为空 |
instructions | 否 | 系统提示 |
max_output_tokens | 否 | 正整数。超过模型或套餐的单次输出上限时按上限发出,并在响应头 X-SciRouter-Max-Tokens-Clamped 与 scirouter.max_tokens_clamped 里写明(不会悄悄截短);不传时按模型的输出上限预扣额度 |
stream | 否 | true 时流式,见下文 |
tools / tool_choice / parallel_tool_calls | 否 | 函数工具({"type": "function", "name", "parameters", "strict"})照常用。厂商的服务端工具(web_search、file_search、code_interpreter、computer_use、image_generation、mcp 等)暂不支持:400 validation_failed,details.reason 为 server_tool_unsupported |
store | 否 | 只认 false:平台不保存响应。不传(SDK 默认不传、厂商默认 true)或写 true 都按 false 处理,响应里如实写 "store": false,透传给上游时也强制写成 false |
reasoning / text / temperature / top_p | 否 | 按线路处理,见下一节 |
metadata / user / safety_identifier | 否 | 用作平台的归因标签与终端用户标识(见可观测性),不转给上游 |
不支持、会被拒绝的(400 validation_failed,details.reason 固定):previous_response_id、conversation、prompt、input 里的 item_reference(stateful_unsupported——平台无状态,请把完整的对话历史放进 input);background(background_unsupported——请同步调用)。
GET / DELETE / cancel 这些按 id 操作已保存响应的接口不存在(404)。
请求体上限 32 MB(与 /messages 相同;Codex 每轮都带整段对话),超过 413 payload_too_large。
请求怎么到上游
#- 模型背后是标了「原生 Responses」的线路(后台在接口能力里勾选,只有 OpenAI / 自定义协议的线路能标):请求原样转发到上游的
/responses,只改几处——model换成上游的名字、去掉metadata/user/safety_identifier、store写false、max_output_tokens写生效值。include、prompt_cache_key、text.verbosity、reasoning.summary、客户端带回的带encrypted_content的推理项、厂商自定义字段都会到达上游;响应事件原样带回,只把model换回平台的 id,id保留厂商给的(Codex 续接推理靠它)。 「原样」说的是请求的格式与字段:鉴权、计费、限额与选线路照旧由平台做,发给厂商的凭据是平台在那家的,你的 SciRouter Key 不会到达厂商。 - 其他线路(普通的 OpenAI 对话补全接口、Anthropic、Gemini 等协议):网关把请求转成对话补全再调,结果再转回 Responses 的形状。
instructions成为 system 消息,input里的消息、function_call、function_call_output逐项转换(输出里的图片挪进紧随的用户消息),函数工具的strict保留,max_output_tokens→max_tokens,reasoning.effort→reasoning_effort,text.format(text/json_object/json_schema)→response_format。 客户端带回的推理项(含encrypted_content)与include: ["reasoning.encrypted_content"]会被丢掉、不报错:它们只对签发的那家有意义,别家模型本来就拿不到;平台在请求日志的scirouter.converted_dropped里记下丢了什么。 转回来的响应与原生的几处差别:id是resp_<请求 id>(与X-Request-Id对应),输出项的 id 形如msg_<请求 id>_0、rs_<请求 id>_0、fc_<call_id>;别家模型的思考过程成为reasoning项的reasoning_text(没有encrypted_content);工具调用在流的末尾整块发出(参数完整)。 - 请求里有只能原样转发的内容(
input_file、按file_id引用的图片、include里厂商工具的输出、厂商定义的客户端工具如local_shell/apply_patch/custom、text.format为grammar等非标准格式、context_management):只走标了「原生 Responses」的线路; 这个模型没有这种线路时返回 503model_unavailable,details.reason为no_capable_endpoint,details.unsupported_content列出是哪些内容 (如["include:code_interpreter_call.outputs"])——平台不会丢掉它们再作答。这类 503 带x-should-retry: false。
响应
#非流式:
json{
"id": "resp_req_…",
"object": "response",
"status": "completed",
"model": "YOUR_MODEL_ID",
"store": false,
"output": [
{ "id": "msg_req_…_0", "type": "message", "role": "assistant", "status": "completed",
"content": [{ "type": "output_text", "text": "…", "annotations": [] }] }
],
"usage": { "input_tokens": 42, "input_tokens_details": { "cached_tokens": 0 }, "output_tokens": 318, "output_tokens_details": { "reasoning_tokens": 0 }, "total_tokens": 360 },
"scirouter": { "request_id": "req_…", "attempts": 1, "cost_quota": 18240, "…": "…" }
}顶层的 scirouter 是平台的扩展块,内容与对话补全里的相同,SDK 会忽略它。一次调用扣了多少配额看 scirouter.cost_quota。
回答写到 max_output_tokens 时 status 是 incomplete、incomplete_details.reason 是 max_output_tokens,和厂商一样。
usage 的 input_tokens 含命中缓存的部分(cached_tokens 单列),output_tokens 含推理(reasoning_tokens 单列)——与账单的维度一一对应。
流式("stream": true)按 Responses 的事件发:每条带 event: 行与连续的 sequence_number,顺序是 response.created → response.in_progress → 每个输出项的 response.output_item.added →
response.content_part.added → response.output_text.delta… → response.output_text.done → response.content_part.done → response.output_item.done(推理项是 response.reasoning_text.delta / done,函数调用是 response.function_call_arguments.delta / done)→ response.completed 或 response.incomplete。和对话补全的流不一样的地方:
- 终态事件(
response.completed/response.incomplete)里的response就是完整的结果,与非流式逐字段相同。平台把它扣到结算之后再发,事件顶层多一个scirouter字段(含这次的cost_quota); - 终态事件之后还有一行
data: [DONE](OpenAI 官方不发、Open Responses 规范要求发;官方 SDK 与 Codex 在终态事件处收尾,多这一行不影响); - 开始输出之后才出错时,发一个
event: response.failed事件(response.status为failed,response.error带code/message/request_id),再[DONE]。没收到终态事件就结束的流,一律按失败处理; - 流里会有以
:开头的心跳注释行,SDK 会忽略。
非流式请求最长约 300 秒,回答可能很长时用流式(见 SDK 与框架「重试与超时」)。
错误
#错误体是 OpenAI 的形状,与对话补全相同(连鉴权失败、路径写错也是),按小写的 error.code 分支,对照见错误码。本接口特有的 400 reason 见上文「请求」。
计价参数与模型变体
#透传到「原生 Responses」线路时,service_tier(取 auto、default 以外的值)会让厂商按另一档单价收费:平台上架了覆盖它的模型变体(id 形如 厂商/模型名:priority)时自动改走变体、按变体的价格结算,响应头 X-SciRouter-Variant-Switch 写触发的参数;没有对应的变体则 400 validation_failed,details.reason 为 price_param_unsupported。
转换走法不带 service_tier 过去,不检查。规则与 Messages 相同。
请求头与响应头
#| 头 | 方向 | 说明 |
|---|---|---|
X-SciRouter-Variant-Switch | 响应 | 这次请求被自动改走了模型变体,值是触发的参数,见上文 |
X-SciRouter-Max-Tokens-Clamped | 响应 | max_output_tokens 超过模型或套餐的单次上限、按上限发出时出现,形如 requested=32000; effective=8192; limit=model |
x-should-retry | 响应 | 值为 false 时这次错误重试不会好(不带 Retry-After 的 503,见错误码) |
其余 X-SciRouter-*、x-ratelimit-* 头与对话补全相同,见可观测性。
缓存、幂等与重试
#Idempotency-Key 与平台响应缓存的规则和对话补全相同,见缓存与幂等;同一个问题用三种格式调是三份缓存,不互相命中。
OpenAI SDK 默认自动重试 429 与 5xx,会先看 x-should-retry。控制台的请求日志按调用格式筛选时,本接口的调用是 responses。