使用 GPT-5.6 Sol 时,“获取已有响应”和“继续生成下一轮响应”是两个不同动作:前者调用 client.responses.retrieve(response_id),只读取指定 Response 对象;后者调用 client.responses.create(..., previous_response_id=response_id),创建一个新 Response,并让服务端把上一轮响应对应的上下文接到新输入之前。实际工程中应保存每次返回的 response.id,而不是把完整响应对象或输出文本当作续接标识。
Responses API 的每次生成都会返回一个 Response 对象。这个对象不仅有可展示的文本,还包含 id、status、output、usage、error 和 incomplete_details 等字段。最重要的是 id:它是以后查询这一轮结果以及建立下一轮关联时使用的唯一标识。
“查询”不会让模型再次推理,也不会生成新答案。它相当于按 ID 读取服务端已经存在的 Response 资源。“继续”则一定会发起新的生成请求,得到新的响应 ID。新响应中的 previous_response_id 指向上一轮,但两轮仍然是两个独立对象。因此,应用数据库最好为每一轮分别记录响应 ID、父响应 ID、业务会话 ID和状态,避免只保留最后一轮而失去排错线索。
先安装或升级官方 Python SDK,并通过环境变量提供 API 密钥。密钥不应硬编码进源码、提交到版本库或写入日志。
python -m pip install --upgrade openai
export OPENAI_API_KEY="your_api_key"
程序中创建客户端时,SDK 会读取环境变量:
from openai import OpenAI
client = OpenAI()
以下示例使用模型 ID gpt-5.6-sol。模型是否对某个项目可用,应以该项目实际权限和接口返回为准;不要在程序里通过捕获所有异常来假装调用成功。
第一轮调用使用 responses.create。对于单段文本输入,可以直接给 input 传字符串。返回后先检查状态,再读取便捷属性 output_text。官方响应结构中的 output 是项目数组,可能包含不同类型的输出项,业务代码不应武断地假设 output[0] 一定是文本消息;只需要聚合文本时,优先使用 SDK 提供的 output_text。
from openai import OpenAI
client = OpenAI()
first = client.responses.create(
model="gpt-5.6-sol",
input="为一个命令行待办应用设计三个核心功能。",
)
print("response_id:", first.id)
print("status:", first.status)
print(first.output_text)
# 实际项目应写入数据库,并与当前用户和业务会话绑定。
saved_response_id = first.id
不要只保存输出文字。输出文字无法用于 retrieve,也不能代替 previous_response_id。同时,不要把一个用户的响应 ID交给另一个用户继续使用;响应 ID应与租户、用户和业务会话建立归属关系。
需要在另一个请求、后台任务或故障恢复流程中重新读取这一轮时,调用 responses.retrieve。Python 方法接收响应 ID并返回 Response 对象:
response_id = saved_response_id
retrieved = client.responses.retrieve(response_id)
print("id:", retrieved.id)
print("status:", retrieved.status)
print("model:", retrieved.model)
print("text:", retrieved.output_text)
底层对应按响应 ID读取资源的 GET 请求。读取成功只说明找到了该对象,不代表它一定生成完成。调用方还要检查 status。常见终态包括 completed、failed、cancelled 和 incomplete;后台生成还可能暂时处于 queued 或 in_progress。只有 completed 才适合按完整结果处理。
def require_completed(response):
if response.status == "completed":
return response
if response.status == "failed":
raise RuntimeError(f"response failed: {response.error}")
if response.status == "incomplete":
raise RuntimeError(
f"response incomplete: {response.incomplete_details}"
)
raise RuntimeError(f"response is not complete: {response.status}")
retrieved = require_completed(
client.responses.retrieve(saved_response_id)
)
print(retrieved.output_text)
如果接口返回未找到或无权访问,首先检查 ID是否被截断、当前 API Key 是否属于正确项目,以及该响应是否仍可被服务端检索。不要把 404 简单解释成“模型没有回答”,因为这是资源查询问题,不是生成内容为空。
要让 GPT-5.6 Sol 基于上一轮继续工作,应再次调用 responses.create,并把上一轮 ID传给 previous_response_id。新问题仍放在 input 中:
previous = client.responses.retrieve(saved_response_id)
require_completed(previous)
second = client.responses.create(
model="gpt-5.6-sol",
previous_response_id=previous.id,
input="把第二个功能拆成可执行的开发任务,并给出验收条件。",
)
print("new response_id:", second.id)
print("previous response_id:", second.previous_response_id)
print(second.output_text)
这里的返回值 second 是新对象,后续第三轮应使用 second.id,而不是始终使用第一轮 ID。若始终引用第一轮,每个新请求都会从同一个分支起点出发,无法自然包含第二轮新产生的信息。
third = client.responses.create(
model="gpt-5.6-sol",
previous_response_id=second.id,
input="将这些任务按依赖关系排序。",
)
print(third.output_text)
previous_response_id 不能与 conversation 同时使用。前者适合应用自己维护一条响应链;如果采用 Conversation 资源管理长期状态,则应按 Conversation 的方式组织输入,不要在同一请求里混用两套状态关联机制。
一个容易忽略的限制是:使用 previous_response_id 时,上一轮的 instructions 不会自动带入下一轮。这允许应用在不同轮次更换开发者指令,但也意味着依赖固定角色、输出语言或安全约束的程序,必须在每次创建新响应时明确传入当前指令。
COMMON_INSTRUCTIONS = (
"使用简体中文回答;涉及代码时先说明假设;"
"不要声称执行过未实际执行的命令。"
)
first = client.responses.create(
model="gpt-5.6-sol",
instructions=COMMON_INSTRUCTIONS,
input="设计一个待办应用的数据模型。",
)
second = client.responses.create(
model="gpt-5.6-sol",
instructions=COMMON_INSTRUCTIONS,
previous_response_id=first.id,
input="现在补充归档功能。",
)
如果第二轮想改变行为,可以传入新的 instructions。这不是修改旧响应,而是控制新响应的生成。工程上可把指令版本与每轮响应 ID一起保存,以便复现为什么同一条响应链在某轮改变了格式或语气。
实际 Web 服务通常从数据库读取某个业务会话的最后响应 ID,生成成功后再原子地更新它。下面的最小封装同时覆盖第一轮和后续轮次:
from typing import Optional
def ask(
prompt: str,
previous_response_id: Optional[str] = None,
):
request = {
"model": "gpt-5.6-sol",
"instructions": "用简体中文给出准确、可验证的回答。",
"input": prompt,
}
if previous_response_id:
request["previous_response_id"] = previous_response_id
response = client.responses.create(**request)
require_completed(response)
return {
"response_id": response.id,
"previous_response_id": response.previous_response_id,
"text": response.output_text,
}
turn1 = ask("为待办应用定义最小数据表。")
turn2 = ask(
"增加软删除字段,并解释迁移策略。",
previous_response_id=turn1["response_id"],
)
并发请求需要额外处理。如果同一业务会话的两个请求同时读到相同的最后响应 ID,它们会形成两个分支。分支本身并非 API 错误,但可能不符合聊天界面的预期。可在数据库更新最后响应 ID时使用版本号或条件更新:只有会话版本仍等于读取时的版本才提交新 ID,否则提示客户端刷新或按产品规则保留分支。
流式响应改变的是接收过程,不改变“每轮都有响应 ID”的基本模型。应用应从流事件中展示增量内容,同时在流结束后保存最终响应的 ID和状态。网络中断时,不要仅凭界面已经出现部分文字就标记为完整;应依据最终事件或随后可获取的 Response 状态决定是否提交业务结果。
如果产品采用后台响应或需要断线续传,还要保存已处理的事件序号,并遵循接口提供的流式恢复参数。普通同步调用则不需要为简单的“继续对话”引入轮询:拿到完成的上一轮响应 ID后,直接用它创建下一轮即可。
网络超时、限流和服务端临时错误可以采用带抖动的指数退避,但创建请求的重试要谨慎:客户端超时并不必然表示服务端没有创建响应。若无法确认结果,盲目再次创建可能产生重复响应和额外费用。应用应记录请求关联信息,并根据 SDK 和平台提供的幂等、请求追踪能力设计恢复流程。
参数错误和权限错误通常不应自动重试。常见排查顺序如下:
retrieve 和 previous_response_id 的是完整 Response ID,而不是消息 ID、业务会话 ID或输出文本。conversation 与 previous_response_id。instructions 都已重新传入。最小验证应覆盖三轮:第一轮创建并保存 ID;第二步用该 ID执行 retrieve,断言返回 ID一致且状态为 completed;第三步创建新响应并传入上一轮 ID,断言新旧 ID不同,且新对象的 previous_response_id 等于上一轮 ID。再增加一项业务断言,确认新回答确实处理了本轮输入,而不是只检查 HTTP 成功。
first = client.responses.create(
model="gpt-5.6-sol",
input="记住项目代号是 Northstar。",
)
assert first.id
same = client.responses.retrieve(first.id)
assert same.id == first.id
assert same.status == "completed"
next_turn = client.responses.create(
model="gpt-5.6-sol",
previous_response_id=first.id,
input="项目代号是什么?只返回代号。",
)
assert next_turn.id != first.id
assert next_turn.previous_response_id == first.id
print(next_turn.output_text)
这套模式的核心很简单:retrieve 负责读旧对象,create 加 previous_response_id 负责生成新一轮。把响应 ID作为受控的持久化状态、逐轮保存父子关系、检查真实状态并在每轮重申必要指令,就能让 GPT-5.6 Sol 的多轮调用具备清晰的恢复、审计和排错路径。