请求、内容与结构化结果
从一次请求建立对象关系
Responses 的输入是文本或有类型的输入项,输出是有类型的项目序列。output_text 是 SDK 提取文本的便利字段,不能据此认定整个响应没有工具调用。应用保存自己的业务请求 ID,服务端返回 response ID;前者跨重试稳定,后者标识一次生成。
在空目录使用 Python 3.12 创建环境。下面采用 openai 2.x 接口,安装后保存锁文件;线上模型权限和参数支持由账户决定,不把某个示例模型当作长期不变的默认值。
python3.12 -m venv .venv
source .venv/bin/activate
python -m pip install 'openai==2.54.0'
python -m pip freeze > requirements.lock设置 OPENAI_API_KEY 和 OPENAI_MODEL 环境变量,不把密钥写进代码。以下保存为 request.py,运行 python request.py 会实际发送请求。
import os
from openai import OpenAI
client = OpenAI(timeout=30.0, max_retries=0)
r = client.responses.create(
model=os.environ['OPENAI_MODEL'],
instructions='简洁回答;不知道时明确说明。',
input=[{'role':'user','content':'用一句话解释 HTTP 超时。'}],
max_output_tokens=200,
store=False,
)
print(r.id, r.status)
for item in r.output:
print(item.type)
print(r.output_text)store=False 不等于端到端零保留,也不能代替组织的数据治理设置;它控制响应存储行为。max_output_tokens 是输出预算,不是答案长度保证,截断时应检查 status 和 incomplete_details。本节在线代码未使用真实账户运行,文本内容不能作为固定断言。
JSON 的三个门槛
业务要抽取订单数量,先约束语法与类型,再校验数量能否执行。将上例创建请求部分替换为下面代码,保留 os、client 定义:
import json
schema = {'type':'object','properties':{
'quantity':{'type':'integer'},
'sku':{'type':['string','null']}
},'required':['quantity','sku'],'additionalProperties':False}
r = client.responses.create(
model=os.environ['OPENAI_MODEL'],
input='需要 2 本书,商品编号未知。',
text={'format':{'type':'json_schema','name':'order','strict':True,'schema':schema}},
store=False,
)
if r.status != 'completed':
raise RuntimeError(f'not complete: {r.status}')
if any(getattr(c,'type',None)=='refusal' for item in r.output
for c in getattr(item,'content',[])):
raise RuntimeError('model refused')
order = json.loads(r.output_text)
if type(order['quantity']) is not int or not 1 <= order['quantity'] <= 10:
raise ValueError('quantity outside business range')
print(order)required 表示键必须出现;null 表示未知,不是空字符串。严格 schema 支持范围需按模型与平台核对。格式正确并不意味着数量真实、商品存在或请求者有权限。拒绝和截断必须先走独立分支,不能把空文本自动修成 {} 后继续下单。
图片输入使用 input_image 内容项及 URL 或 data URL,文本使用 input_text;文件引用与检索不是一件事。发送文件前先检查类型、大小和权限;上传成功只证明有文件对象,不证明模型已引用正确页码。
参阅 Responses 参考 与 结构化输出。
上传 PDF 后再引用文件身份
保存pdf_request.py,准备有权发送的source.pdf,沿用账户与环境后运行。它是完整在线SDK入口,会上传文件并计费生成,本次未在线运行。这里的输入类型是input_file,与图片的input_image不同。
import os
from pathlib import Path
from openai import OpenAI
path=Path('source.pdf')
if path.suffix.lower()!='.pdf' or not path.read_bytes().startswith(b'%PDF-'):
raise ValueError('expected a PDF source')
with OpenAI(timeout=60,max_retries=0) as c:
with path.open('rb') as file:
uploaded=c.files.create(file=file,purpose='user_data')
try:
r=c.responses.create(model=os.environ['OPENAI_MODEL'],store=False,
input=[{'role':'user','content':[
{'type':'input_file','file_id':uploaded.id},
{'type':'input_text','text':'概括退款条件;说明依据页码,没有依据则明确说明。'}]}])
if r.status!='completed':raise RuntimeError('file answer incomplete')
print(r.output_text)
finally:c.files.delete(uploaded.id)文件对象的id只定位平台文件,不是应用授权凭证;在应用数据库绑定owner、版本与保留期。删除上传对象不等于删除全部业务日志或其他存储副本。扩展名和文件头只是快速排错,完整类型/大小检查与内容安全由上传入口承担。要求模型给页码并不保证引用正确,应对照原PDF检查。
内联图片只需要将上述文件项替换为数据URL(同样先确认授权和支持格式):
import base64
image_bytes=Path('sample.png').read_bytes()
image_part={'type':'input_image','image_url':'data:image/png;base64,'+base64.b64encode(image_bytes).decode('ascii')}此片段复用同文件的Path;图片转换不上传Files,反复发送会重复输入。不要把图片base64写入日志,诊断记录受控文件引用与内容哈希即可。
练习
将合法数量范围改为 1–5。离线检查 2、0、6、True、"2",指出哪些应通过;解释为什么 isinstance(True, int) 不能满足这里的契约。
先记录自己的输出与解释,再核对本章答案。