结构化输出
用 JSON Schema 约束回答,并验证返回值。
结构化输出将回答约束为指定的 JSON 结构;接收端仍需检查终态、解析 JSON 并验证业务字段。
选择支持的模型
先查看 GET /openai/v1/models 中的 supported_parameters:候选模型应列有 response_format 和 structured_outputs。2026-10-12,gpt-5.6-sol 的 Chat Completions json_schema + strict 和 claude-opus-5 的 Messages output_config 均实测通过,下面分别使用它们。
deepseek-flash 虽列有上述参数,本次 json_schema 请求仍返回 400,因此本页不使用它演示 schema 约束。目录声明不能替代实际响应验证。json_object(JSON mode)只表达 JSON 格式要求,不等于 schema 约束;需要固定字段和类型时使用本页的 json_schema。
1. 准备终端
将 Praxis 云 API Key 放入环境变量 PRAXIS_API_KEY;获取方式见 Key 与鉴权。不要把 Key 写入代码。准备 Python 和 uv;TypeScript 示例使用 Bun。
test -n "$PRAXIS_API_KEY"期望:退出码为 0。示例默认使用 https://api.deepraxis.ai。切换环境时,将 PRAXIS_API_BASE 设为相应 API 域名,不带末尾斜杠或 API 路径。
将每段 Python 保存为标明的文件名,以 uv run -q --no-project --with openai --with anthropic python <文件名> 执行。各段代码独立运行。
2. 使用 json_schema 和 strict
保存为 structured-output.py,运行 uv run -q --no-project --with openai --with anthropic python structured-output.py。
response_format.json_schema 中同时给出 name、strict: true 和 schema。本例将所有属性列入 required,并用 additionalProperties: false 禁止额外字段。
import json
import os
from openai import OpenAI
client = OpenAI(api_key=os.environ['PRAXIS_API_KEY'],
base_url=os.environ.get('PRAXIS_API_BASE', 'https://api.deepraxis.ai') + '/openai/v1')
schema = {'type':'object', 'properties':{
'city':{'type':'string'}, 'temperature':{'type':'integer'}},
'required':['city','temperature'], 'additionalProperties':False}
response = client.chat.completions.create(model='gpt-5.6-sol', max_tokens=1024,
messages=[{'role':'user','content':'Return JSON for Paris with temperature 22.'}],
response_format={'type':'json_schema','json_schema':{
'name':'weather','strict':True,'schema':schema}})
print('X-Request-Id:', response._request_id)
choice = response.choices[0]
assert choice.finish_reason == 'stop' and not choice.message.refusal
value = json.loads(choice.message.content)
assert set(value) == {'city','temperature'}
assert isinstance(value['city'], str) and type(value['temperature']) is int
assert value == {'city':'Paris','temperature':22}, value
print(value)期望:输出 {'city': 'Paris', 'temperature': 22},退出码为 0。代码不剥除 Markdown 围栏;只有直接可解析、字段和类型符合要求的 JSON 才通过。
先检查 finish_reason 和 refusal,再解析内容。被截断或拒绝的回答不能当成符合 schema 的业务结果;流式回答应先收齐文本再解析。
3. 用 TypeScript 验证相同结果
保存为 structured-output.ts,运行 bun structured-output.ts;无需安装 SDK。
import assert from 'node:assert/strict';
const base = process.env.PRAXIS_API_BASE ?? 'https://api.deepraxis.ai';
const schema = {type: 'object', properties: {
city: {type: 'string'}, temperature: {type: 'integer'}},
required: ['city', 'temperature'], additionalProperties: false};
const response = await fetch(`${base}/openai/v1/chat/completions`, {
method: 'POST', headers: {'Authorization': `Bearer ${process.env.PRAXIS_API_KEY}`, 'Content-Type': 'application/json'},
body: JSON.stringify({model: 'gpt-5.6-sol', max_tokens: 1024,
messages: [{role: 'user', content: 'Return JSON for Paris with temperature 22.'}],
response_format: {type: 'json_schema', json_schema: {name: 'weather', strict: true, schema}}}),
});
console.log('X-Request-Id:', response.headers.get('x-request-id'));
const data = await response.json();
assert(response.ok, JSON.stringify(data));
const choice = data.choices[0];
assert.equal(choice.finish_reason, 'stop');
assert(!choice.message.refusal);
const value = JSON.parse(choice.message.content);
assert.deepEqual(value, {city: 'Paris', temperature: 22});
console.log(value);期望:同样得到 city=Paris、整数 temperature=22,没有额外字段,退出码为 0。
4. 使用 Messages 的 output_config
Messages 将 schema 放在 output_config.format 下,type 为 json_schema;不要把 Chat Completions 的 response_format 或 json_schema.name 外壳搬过来。这里也不添加 OpenAI 的 strict 字段。
保存为 structured-output-anthropic.py,运行 uv run -q --no-project --with openai --with anthropic python structured-output-anthropic.py。
import json
import os
from anthropic import Anthropic
client = Anthropic(api_key=os.environ['PRAXIS_API_KEY'],
base_url=os.environ.get('PRAXIS_API_BASE', 'https://api.deepraxis.ai') + '/anthropic')
schema = {'type':'object', 'properties':{
'city':{'type':'string'}, 'temperature':{'type':'integer'}},
'required':['city','temperature'], 'additionalProperties':False}
raw = client.messages.with_raw_response.create(model='claude-opus-5', max_tokens=1024,
messages=[{'role':'user','content':'Return JSON for Paris with temperature 22.'}],
output_config={'format':{'type':'json_schema','schema':schema}})
print('X-Request-Id:', raw.headers.get('x-request-id'))
message = raw.parse()
assert message.stop_reason == 'end_turn'
value = json.loads(''.join(block.text for block in message.content if block.type == 'text'))
assert set(value) == {'city','temperature'}
assert isinstance(value['city'], str) and type(value['temperature']) is int
assert value == {'city':'Paris','temperature':22}, value
print(value)期望:stop_reason=end_turn,JSON 验证通过并输出相同对象,退出码为 0。
实测失败与处理
deepseek-flash 的 schema 请求返回 HTTP 400,错误消息的固定部分为:
This response_format type is unavailable now
消息还会附带当次请求标识。切换为本页已实测通过的 gpt-5.6-sol 后通过。不要将 HTTP 200、Markdown 中的一段 JSON 或能解析的普通 JSON 当成 schema 约束已生效;保留示例中的完整验证。