OpenAI Responses API 进阶用法:从入门到精通
我为什么要折腾这个 API
上个月我在重构一个客服工单系统,原来用的是 Chat Completions API。痛点很明显:模型需要调用我们内部的工单查询工具,每次工具调用结果回来后,我得手动把完整的消息历史拼回去再发一次请求。消息越拼越长,token 费用蹭蹭涨,代码里全是状态管理的胶水代码。
后来我把服务迁到了 OpenAI 的 Responses API(2025 年 3 月发布的),核心原因就一个:服务端状态管理。模型的多轮工具调用状态可以存在 OpenAI 那边,我只需要传一个 previous_response_id 就能续上对话。下面是我踩完坑之后总结的完整实践。
基础:一次最简单的调用
先看最小的可用例子:
from openai import OpenAI
client = OpenAI()
response = client.responses.create(
model="gpt-4.1",
input="用一句话解释什么是幂等性"
)
print(response.output_text)
注意 response.output_text 这个便捷属性,它把 output 数组里所有文本自动拼接好了。在 Chat Completions 里你得写 response.choices[0].message.content,初学者经常在这里写错索引。
进阶一:服务端状态,省掉 80% 的胶水代码
这是我最看重的特性。多轮对话不用再传完整历史:
# 第一轮
r1 = client.responses.create(
model="gpt-4.1",
input="我的订单号是 A10293,查询一下物流状态"
)
# 第二轮:只传新内容和上一轮的 ID
r2 = client.responses.create(
model="gpt-4.1",
previous_response_id=r1.id,
input="如果超时了该怎么申请赔偿?"
)
print(r2.output_text)
默认状态下对话会保留 30 天。第一个坑:如果涉及用户隐私数据,记得关掉存储:
response = client.responses.create(
model="gpt-4.1",
input="...",
store=False # 不落盘,也就不能用 previous_response_id 了
)
进阶二:内置工具,不用自己写 glue code
Responses API 把联网搜索、文件搜索、代码解释器做成了原生参数。我的工单系统里查物流就用了 web search:
response = client.responses.create(
model="gpt-4.1",
tools=[{
"type": "web_search_preview",
"search_context_size": "medium" # low / medium / high
}],
input="查一下 2024 年欧盟 AI Act 对客服聊天机器人的合规要求"
)
# 引用来源
for item in response.output:
if item.type == "message":
for annotation in item.content[0].annotations:
print(annotation.url_citation.url)
第二个坑:web search 的 token 消耗比想象中大,search_context_size 设成 high 一次请求能吃掉上万 token。我的做法是日常查询用 low,需要深度资料时才切 medium。
文件搜索也一样简单,先建 vector store,上传文档,然后:
response = client.responses.create(
model="gpt-4.1",
tools=[{
"type": "file_search",
"vector_store_ids": ["vs_abc123"],
"max_num_results": 5
}],
input="退货政策里关于定制商品是怎么规定的?"
)
进阶三:结构化输出,直接吐 JSON
工单自动分类这个场景,我需要模型输出严格的 JSON。用 text.format 而不是老的 response_format:
import json
response = client.responses.create(
model="gpt-4.1",
input="用户反馈:『APP 打开就闪退,小米 14,系统最新版』",
text={
"format": {
"type": "json_schema",
"name": "ticket_classification",
"schema": {
"type": "object",
"properties": {
"category": {"type": "string",
"enum": ["bug", "billing", "feature_request"]},
"severity": {"type": "string",
"enum": ["low", "medium", "high"]},
"device_info": {"type": "string"}
},
"required": ["category", "severity"],
"additionalProperties": False
},
"strict": True
}
}
)
data = json.loads(response.output_text)
strict: true 配合 additionalProperties: False 是关键,不然校验经常失败。这是我从错误里学到的——第一次没加 additionalProperties: False,strict 模式直接拒绝了请求。
进阶四:多模态输入
图片直接放在 input 数组里,这个设计比 Chat Completions 的 content blocks 更直观:
response = client.responses.create(
model="gpt-4.1",
input=[
{
"role": "user",
"content": [
{"type": "input_text", "text": "这张报错截图是什么问题?"},
{
"type": "input_image",
"image_url": "https://example.com/screenshot.png",
"detail": "high" # 低分辨率够用就设 low,省 token
}
]
}
]
)
流式输出 + 事件类型
流式这块 Responses API 的事件粒度比 Chat Completions 细很多:
stream = client.responses.create(
model="gpt-4.1",
input="写一段产品更新公告",
stream=True
)
for event in stream:
if event.type == "response.output_text.delta":
print(event.delta, end="", flush=True)
elif event.type == "response.completed":
print(f"\n[完成] 用量: {event.response.usage.total_tokens}")
我一开始只处理了 delta 事件,结果漏了工具调用相关的事件类型,前端展示工具执行状态时全是空白。后来老老实实根据 event.type 分支处理才正常。
实用技巧汇总
- reasoning 模型要用
max_output_tokens,不是max_tokens。参数名变了,老代码迁移时最容易在这里报错。 - 计时和用量都直接挂在
response.usage上,做成本监控很方便,不用自己算。 instructions参数代替 system message,语义更清晰,而且可以配合previous_response_id在多轮中保持不变的系统指令。- store + previous_response_id 有 30 天限制,长期对话记得定期把关键内容抽出来存自己的数据库。
诚实的局限性评估
用了两个月,说点实在的:
- web search 和 file search 只有在支持的地区/账号下可用,而且价格比纯推理贵不少,高流量场景要先算账。
previous_response_id是线性的,不能做树状分支对话。如果需要“回到三步前重试”,还是得自己管理完整历史。- 状态存放在 OpenAI 那边,对数据合规敏感的企业这是个需要过安全评审的点,
store=False能缓解但会失去状态管理的好处。 - SDK 的类型提示有些地方还没跟上新 API,
response.output里的 item 类型要靠运行时判断。
总体来说,如果你在做多轮工具调用、RAG 或需要结构化输出的应用,Responses API 值得迁移——我把工单系统的工具调用代码从 300 行减到了不到 100 行。但如果只是简单的单轮问答,Chat Completions 也够用,不必为了迁移而迁移。