结构化输出:JSON mode 与 JSON Schema,同一套请求格式
结构化输出就是让模型返回 JSON 而不是一段文字——要么是任意合法的 JSON 对象(json_object),要么是遵循你指定 schema 的 JSON(json_schema)。Router One 遵循 OpenAI Chat Completions 规范,response_format 字段直接附在你发给目录中任何对话模型的同一个请求上。网关先检查信封:schema 部分不完整时,在调用任何模型之前就返回 400;完整时把 response_format 原样转发给模型,不做改写。Schema 是否被强制执行取决于模型——支持程度和严格度因模型而异,所以拿到回复后一定要解析并校验。
用 json_object 还是 json_schema?
两者都放在 response_format 里,按你需要多少「形状约束」来选:
json_object——JSON mode
模型返回一个语法合法的 JSON 对象,键名和嵌套由你的 prompt 决定。务必在 prompt 里说明要 JSON 并描述字段——这个标记本身不会定义字段。
json_schema——带名字的 schema
附上一份带 name 的 JSON Schema,可选 description 和 strict: true。支持它的模型会把输出约束到这个 schema;不支持的模型照样回复,只是没有约束。这个差别正是必须在客户端校验的原因。
JSON mode 请求
从 /models 目录选任意一个对话模型 ID。system 消息负责列字段,response_format 负责声明模式:
curl https://api.router.one/v1/chat/completions \
-H "Authorization: Bearer sk-your-router-one-key" \
-H "Content-Type: application/json" \
-d '{
"model": "<model-id-from-/models>",
"messages": [
{"role": "system", "content": "只返回一个 JSON 对象,键为 city 和 date。"},
{"role": "user", "content": "抽取城市和日期:9 月 3 日在上海开会。"}
],
"response_format": {"type": "json_object"}
}'JSON Schema 请求
json_schema 对象必须带 name 和 schema;description 与 strict 可选,会随其余字段一起转发:
curl https://api.router.one/v1/chat/completions \
-H "Authorization: Bearer sk-your-router-one-key" \
-H "Content-Type: application/json" \
-d '{
"model": "<model-id-from-/models>",
"messages": [{"role": "user", "content": "抽取城市和日期:9 月 3 日在上海开会。"}],
"response_format": {
"type": "json_schema",
"json_schema": {
"name": "meeting",
"strict": true,
"schema": {
"type": "object",
"properties": {
"city": {"type": "string"},
"date": {"type": "string"}
},
"required": ["city", "date"],
"additionalProperties": false
}
}
}
}'转发前网关检查什么
type 为 json_schema 时,Router One 会校验信封;不完整的请求在调用任何模型之前就以 400 invalid_request 拒绝——请求根本没到模型,也就没有可计费的 token 用量。三条报错原文如下:
| 请求情况 | 400 报错原文 | 修法 |
|---|---|---|
| type 为 json_schema 但没有 json_schema 对象 | response_format.json_schema is required when response_format.type is json_schema | 补上带 name 和 schema 的 json_schema 对象 |
| json_schema 缺 name | response_format.json_schema.name is required | 给 schema 起一个短标识,比如 meeting |
| json_schema 缺 schema | response_format.json_schema.schema is required | 把 JSON Schema 对象放到 schema 字段下 |
- 信封完整时原样转发——name、description、strict、schema 都在。网关不裁剪、不改写、不重新校验 schema 正文。
- 网关也不会拿你的 schema 去校验模型的回复。强制执行是模型的事,验证是你的事。
支持程度与严格度因模型而异
- json_object 支持面很广:你会拿到格式良好的 JSON,形状由 prompt 决定。
- json_schema 与 strict 只有一部分模型会遵守。模型不应用 schema 时你照样收到 200 和回复——只是没有约束。走原生非 OpenAI 协议的模型可能只认 json_object,或者完全不应用 schema。
- /models 目录按模型标注了对话、流式、工具调用、视觉能力,没有「结构化输出」标记。动手前先对目标模型发一个小请求,在 Trace 里看回复再决定。
- 需要一个跨模型家族都成立的形状?声明一个工具并用 tool_choice 强制调用——在支持工具调用的模型上,参数会以遵循你 parameters schema 的 JSON 字符串返回。无论哪种方式,用之前先校验。
Schema 方言:$defs、$ref 与候选换路
有些模型线路不接受 JSON Schema 里的 $defs 和 $ref,因为它们的原生 schema 方言没有对应写法。Router One 把这种拒绝视为该候选线路自身的问题,而不是模型的问题:请求会换到同一个模型的下一个候选继续,这次拒绝也不计入模型的健康度。如果所有候选都拒绝这种方言,400 才会返回给你——把定义内联(展开每个 $ref),同一份 schema 就能通过。最终服务这次请求的候选,就是 Dashboard → Logs 里显示的那条。
流式与 SDK 用法
照常设置 stream: true。json_schema 下 JSON 会以 content delta 的形式按正常 SSE 分块到达——累积 content 直到最后一块再一次性解析,绝不要在流中途解析半个对象。用 OpenAI SDK 时,response_format 就是一个普通的关键字参数:
from openai import OpenAI
import json
client = OpenAI(
base_url="https://api.router.one/v1",
api_key="sk-your-router-one-key",
)
schema = {
"type": "object",
"properties": {"city": {"type": "string"}, "date": {"type": "string"}},
"required": ["city", "date"],
"additionalProperties": False,
}
resp = client.chat.completions.create(
model="<model-id-from-/models>",
messages=[{"role": "user", "content": "抽取城市和日期:9 月 3 日在上海开会。"}],
response_format={
"type": "json_schema",
"json_schema": {"name": "meeting", "strict": True, "schema": schema},
},
)
data = json.loads(resp.choices[0].message.content)
# 使用前先按 schema 校验 data在 Dashboard → Logs 里看结果
每次尝试都是一行请求记录,带模型、Token、花费、延迟和状态标记;点开可看请求详情。网关自身检查返回的 400,状态显示 400 并附上面的报错原文——改请求。回复是一段文字或形状松散的 JSON 的 200,说明你指定的模型没有应用 schema——收紧 prompt、加一步校验重试,或者换模型。改代码之前,Trace 已经告诉你遇到的是哪一种。
常见问题
json_object 和 json_schema 有什么区别?
json_object(JSON mode)只保证回复是语法合法的 JSON 对象,键名由 prompt 决定。json_schema 附上一份带名字的 JSON Schema,可选 strict: true,支持它的模型会把输出约束到这个形状。两者都通过 /v1/chat/completions 上同一个 response_format 字段发送。
Router One 保证回复符合我的 schema 吗?
不保证。网关校验请求信封,并把 response_format 原样转发给模型;它不改写 schema,也不拿 schema 去检查模型输出。是否强制执行取决于你指定的模型,且因模型而异,所以使用前请在客户端解析并校验回复。
schema 算 Token 吗?
算——schema 随 prompt 一起发送,每次请求都按输入 Token 计费;JSON 回复按该模型的公示费率计为输出 Token。被网关自身检查以 400 拒绝的请求没有到达模型,没有 Token 用量。Dashboard → Logs 的每请求 Trace 会立刻显示大 schema 对 Token 和费用的影响。
json_schema 可以配合流式用吗?
可以。设置 stream: true 后,JSON 会以 content delta 的形式、按和其他流式回复一样的 SSE 分块格式到达。累积 content 片段直到流结束再一次性解析——半个对象是解析不了的。分块格式、超时与取消见 LLM 流式输出指南。
该用工具调用还是结构化输出?
结构化输出适合「只要固定 JSON 形状的回复、没有真函数要跑」。工具调用适合「要执行动作」:模型请求调你的函数,你把结果喂回去。如果目标模型不遵守 json_schema,又需要符合 schema 的回复,强制调用单个工具是跨模型通用的替代方案——参数会遵循你的 parameters schema。很多应用两者并用。
这个 400 是什么意思,会扣费吗?
指向 response_format.json_schema、.name 或 .schema 的 400 invalid_request,表示信封不完整,网关在调用任何模型之前就拦下了——没有任何计费。补上缺的字段重发即可。其他 400 来自模型线路本身;最常见的是所有候选都重复拒绝 $defs/$ref,把 schema 展平就能解决。