对话补全
POST /v1/chat/completions —— 提交一段对话,由模型续写下一轮回复。
先理解这个接口#
对话补全接口只做一件事:接收完整的对话内容,由模型续写下一轮回复后返回。该接口不具备记忆能力,服务端不保存任何上下文;因此进行多轮对话时,需要把此前所有轮次连同模型上一次的回复一并重新提交。
对话内容放在 messages 数组里,按时间顺序排列。每条消息有 role 和 content 两个必填字段,role 表明这句话是谁说的。
一个三轮对话的 messages:注意 assistant 那条是模型之前说的,由你带回来
[ { "role": "system", "content": "你是一名简洁的技术支持助理,只用中文回答。" }, { "role": "user", "content": "我的订单还能退款吗?" }, { "role": "assistant", "content": "可以的,支付后 7 天内都能申请全额退款。" }, { "role": "user", "content": "那退到账要多久?" }]第一次调用#
下面三段代码做同一件事:问一个问题,打印模型的回答。
curl:最小请求
curl https://ogrouter.ai/v1/chat/completions \ -H "Authorization: Bearer $AIROUTER_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "openai/gpt-5.2", "messages": [ { "role": "user", "content": "用一句话解释什么是向量数据库" } ] }'响应:回答在 choices[0].message.content 里
{ "id": "chatcmpl-abc123", "object": "chat.completion", "created": 1754126400, "model": "openai/gpt-5.2", "choices": [ { "index": 0, "message": { "role": "assistant", "content": "向量数据库是一种按语义相似度而不是精确匹配来检索数据的存储系统。" }, "finish_reason": "stop" } ], "usage": { "prompt_tokens": 21, "completion_tokens": 28, "total_tokens": 49 }}finish_reason 说明模型停止生成的原因:stop 表示正常结束,length 表示达到了 max_tokens 上限(回复被截断,通常需要调高该上限后重试),tool_calls 表示模型请求调用工具。
Python:用官方 openai 库,改 base_url 即可
import osfrom openai import OpenAI
client = OpenAI( api_key=os.environ["AIROUTER_API_KEY"], base_url="https://ogrouter.ai/v1",)
response = client.chat.completions.create( model="openai/gpt-5.2", messages=[ {"role": "system", "content": "你是一名简洁的技术写作者。"}, {"role": "user", "content": "用一句话解释什么是向量数据库"}, ],)
print(response.choices[0].message.content)print(response.usage.total_tokens, "tokens")Node.js:同样只改 baseURL
import OpenAI from 'openai';
const client = new OpenAI({ apiKey: process.env.AIROUTER_API_KEY, baseURL: 'https://ogrouter.ai/v1',});
const response = await client.chat.completions.create({ model: 'openai/gpt-5.2', messages: [ { role: 'system', content: '你是一名简洁的技术写作者。' }, { role: 'user', content: '用一句话解释什么是向量数据库' }, ],});
console.log(response.choices[0].message.content);Java(openai-java SDK)
import com.openai.client.OpenAIClient;import com.openai.client.okhttp.OpenAIOkHttpClient;import com.openai.models.chat.completions.ChatCompletion;import com.openai.models.chat.completions.ChatCompletionCreateParams;
OpenAIClient client = OpenAIOkHttpClient.builder() .baseUrl("https://ogrouter.ai/v1") .apiKey(System.getenv("AIROUTER_API_KEY")) .build();
ChatCompletionCreateParams params = ChatCompletionCreateParams.builder() .model("openai/gpt-5.2") .addSystemMessage("你是一名简洁的技术写作者。") .addUserMessage("用一句话解释什么是向量数据库") .build();
ChatCompletion response = client.chat().completions().create(params);System.out.println(response.choices().get(0).message().content().orElse(""));请求字段#
下表逐条说明请求体支持的字段。控制随机性的采样参数(temperature、top_p 等)单独列在下一节。未列出的字段会原样转发给上游。
modelstring必填- 模型名。支持别名、
:变体与逗号降级链,语法见 模型与路由。 messagesarray必填- 整段对话,按时间顺序排列;角色与多轮写法见上面的"先理解这个接口"。
content允许为null(工具调用回合的合法形态),会被规范化成空文本部分。 max_tokensinteger- 本次生成的输出上限。传了它,预扣的冻结额会更贴近实际支出;不传时按模型的最大输出能力估算,见 计费口径。
streambooleantrue时返回text/event-stream,帧序列见下一节。stream_optionsobject- 传
{"include_usage": true}才会在流的末尾多收到一帧用量统计;不传的话整条流里没有任何用量信息。 tools / tool_choicearray / string- 工具调用,透传。
parallel_tool_calls也透传;Anthropic 上游用方向相反的disable_parallel_tool_use表达,网关会自动取反。 response_formatobject- 结构化输出。是否可用取决于目标端点,可配合
provider.require_parameters强制只选支持它的线路。 reasoningobject- 跨协议统一的思考开关:
enabled打开思考,effort取minimal/low/medium/high,max_tokens给思考预算,exclude表示照样思考但不返回思考内容。出站时映射成上游各自的字段(Anthropic 的thinking、OpenAI 的reasoning_effort);模型不支持时以上游行为为准,要强制只走支持它的线路请配合provider.require_parameters。 providerobject- 路由偏好(本平台扩展):白名单、黑名单、排序、价格上限、区域与合规约束。见 模型与路由。
modelsarray- 模型级降级链。它不是
model里逗号写法的替代品,而是与之合并去重(顺序为model的逗号链 →models→provider.models);主模型加兜底最多 4 个,多出来的静默忽略。 transformsarray- 为兼容 OpenRouter 保留的字段:会被接收并记录,但当前不会对请求产生任何变换。输入超出上下文窗口时,请自行裁剪内容或改用长上下文模型。
一个带路由约束的请求体
{ "model": "anthropic/claude-fable-5", "messages": [{ "role": "user", "content": "总结这份会议记录" }], "provider": { "sort": "price", "only": ["cl-sp", "cl-of"], "allow_fallbacks": true, "max_price": { "prompt": "5.00000000", "completion": "20.00000000" } }}采样参数#
模型每生成一个词,实际是在一组候选词上计算概率分布后选取其一。下列参数用于控制这一选取过程,它们均为可选,不传时采用模型自身的默认值。
provider.require_parameters,见模型与路由。流式输出#
每一帧的格式是 data: <JSON>,JSON 结构为 chat.completion.chunk,流以 data: [DONE] 结束。[DONE] 不是合法 JSON,客户端必须先判断这个哨兵值再解析。
error 字段的数据、紧接着 [DONE]。客户端如果不检查每帧是否含 error,会把失败请求当成正常结束。典型的一段流(: ping 是 SSE 注释行,用于防止中间层超时,标准解析器会忽略)
data: {"choices":[{"index":0,"delta":{"role":"assistant"},"finish_reason":null}]}data: {"choices":[{"index":0,"delta":{"content":"维多利亚港的风"},"finish_reason":null}]}: pingdata: {"choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}data: {"choices":[],"usage":{"prompt_tokens":26,"completion_tokens":48,"total_tokens":74}}data: [DONE]usage 帧照常发送,因此用量统计逻辑不必按上游分别处理。估算标记不在响应体内:非流式请读响应头 X-AiRouter-Usage-Estimated,流式请在 调用日志 中查看——响应头在流开始时已经发出。Python:逐字打印,帧里没有 content 的时候要跳过
import osfrom openai import OpenAI
client = OpenAI(api_key=os.environ["AIROUTER_API_KEY"], base_url="https://ogrouter.ai/v1")
stream = client.chat.completions.create( model="openai/gpt-5.2", messages=[{"role": "user", "content": "写一首关于香港雨季的短诗"}], stream=True, stream_options={"include_usage": True}, # 不传这个就收不到用量帧)
for chunk in stream: # 用量帧的 choices 是空数组,直接访问 [0] 会抛 IndexError if not chunk.choices: if chunk.usage: print(f"\n\n共 {chunk.usage.total_tokens} tokens") continue delta = chunk.choices[0].delta.content if delta: print(delta, end="", flush=True)Node.js:同样的循环
import OpenAI from 'openai';
const client = new OpenAI({ apiKey: process.env.AIROUTER_API_KEY, baseURL: 'https://ogrouter.ai/v1',});
const stream = await client.chat.completions.create({ model: 'openai/gpt-5.2', messages: [{ role: 'user', content: '写一首关于香港雨季的短诗' }], stream: true, stream_options: { include_usage: true },});
for await (const chunk of stream) { const delta = chunk.choices[0]?.delta?.content; if (delta) process.stdout.write(delta); if (chunk.usage) console.log(`\n\n共 ${chunk.usage.total_tokens} tokens`);}Java:逐字打印,用量帧要单独处理
import com.openai.core.http.StreamResponse;import com.openai.models.chat.completions.ChatCompletionChunk;import com.openai.models.chat.completions.ChatCompletionCreateParams;import com.openai.models.chat.completions.ChatCompletionStreamOptions;
ChatCompletionCreateParams params = ChatCompletionCreateParams.builder() .model("openai/gpt-5.2") .addUserMessage("写一首关于香港雨季的短诗") // 不设 includeUsage 就收不到用量帧 .streamOptions(ChatCompletionStreamOptions.builder().includeUsage(true).build()) .build();
try (StreamResponse<ChatCompletionChunk> stream = client.chat().completions().createStreaming(params)) { stream.stream().forEach(chunk -> { // 用量帧的 choices 是空数组,先判空再取下标 if (chunk.choices().isEmpty()) { chunk.usage().ifPresent(u -> System.out.printf("%n%n共 %d tokens%n", u.totalTokens())); return; } chunk.choices().get(0).delta().content().ifPresent(System.out::print); });}用量口径#
- 非流式响应总是带
usage;流式默认不带,需要显式传stream_options.include_usage。 prompt_tokens是全部输入 token,包含缓存读与缓存写部分;缓存读在prompt_tokens_details.cached_tokens,缓存写在cache_creation_input_tokens。按标准输入价计费的是三者相减后的余量。completion_tokens只含可见输出,不含思考 token;思考量在completion_tokens_details.reasoning_tokens里单独给出、单独计费。- 用量帧只报 token,不含金额。非流式的费用在响应头,流式的费用在 调用日志。
与 OpenAI 的已知差异#
Anthropic 协议#
除上述格式外,网关还提供 POST /v1/messages,遵循 Anthropic 的 Messages 格式——这是另一套写法不同、能力相当的对话协议:system 不放在 messages 内,而是作为独立的顶层字段;返回正文是 content 数组而非单个字符串。若现有代码使用 Anthropic 官方 SDK,指向本平台即可运行;否则使用上述 /v1/chat/completions 即可。两条端点背后是同一批模型。
POST /v1/messages 与 Anthropic Messages API 兼容:model 一样支持别名、:变体 与逗号降级链,流式天然带用量(message_start / message_delta 里就有),不需要 include_usage。
curl:Anthropic 格式的同一个问题
curl https://ogrouter.ai/v1/messages \ -H "X-Api-Key: $AIROUTER_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "anthropic/claude-fable-5", "max_tokens": 256, "system": "你是一名简洁的技术写作者。", "messages": [ { "role": "user", "content": "用一句话解释什么是向量数据库" } ] }'provider / models / transforms 这些扩展只在 OpenAI 协议的请求体里被识别。写在 /v1/messages 上会被当成未知字段原样转发给上游,通常会被上游拒绝并返回 400。要用路由偏好就走 /v1/chat/completions,或者把降级链写进 model 的逗号形式。anthropic-version无需传递:网关在出站时统一填入2023-06-01。anthropic-beta目前不转发;客户端的自定义请求头不会传递到上游。- 错误封装是 Anthropic 形状,没有
code字段,分支要靠 HTTP 状态与error.type,见 错误码。