Background Mode 解决的是“任务很长,但 HTTP 请求、浏览器标签或 Serverless Function 不应一直占着连接”。创建后立即持久化 response ID;运行中只识别 queued 与 in_progress,离开这两个状态就停止轮询,再分别处理 completed、failed、cancelled 和 incomplete。它不会自动让模型更便宜、更快,也不能替代 Batch、SSE 或 WebSocket。
官方资料(2026-10-30 核对):Background mode · Retrieve response · Webhooks · Cancel response
Background Mode、同步、SSE、WebSocket 与 Batch 怎么选?
| 方案 | 核心问题 | 结果获取 | 适合场景 |
|---|---|---|---|
| 同步 Responses | 一次请求内完成 | 连接结束时拿完整对象 | 短任务、后端可稳定等待 |
| 普通 SSE | 降低首 Token 等待 | 同一 HTTP 连接接收事件 | 聊天 UI、长文本逐步展示 |
| Background Mode | 长任务脱离原连接继续 | 按 response ID 轮询、Webhook 或恢复流 | 深度推理、复杂长任务、易超时环境 |
| Responses WebSocket | 多回合 Agent 复用持久连接 | 重复 response.create 与 stream_id lane | 工具调用多、并发会话、低回合开销 |
| Batch API | 大量离线请求集中处理 | 批次状态与结果文件 | 评测、分类、嵌入、批量摘要 |
如果只想显示实时增量,先看 API Streaming 教程;多工具长链路需要持久连接,参见 Responses WebSocket;成千上万条不要求即时返回的任务,参见 Batch API。
第一步:创建后台 Response 并立即保存 ID
在服务端调用 Responses create,设置 background: true。模型名用账户当前可用模型替换,不能把示例模型当成所有 Project 永久可用。
import OpenAI from "openai";
const client = new OpenAI();
const response = await client.responses.create({
model: "YOUR_MODEL",
input: "生成一份需要较长推理时间的风险分析。",
background: true
});
// 立即持久化,不能只放在浏览器内存
await db.jobs.insert({
userId,
responseId: response.id,
status: response.status,
createdAt: new Date()
});
return {
jobId: response.id,
status: response.status
};标准 API Key 只放在受信任服务端。前端可以拿你自己的 job ID 或经过授权的 response ID 查询应用后端,但不应直接获得 OpenAI Key。生产服务还要将 response ID 与当前用户、Project、请求摘要、取消权限和创建时间绑定,避免越权读取其他人的结果。
第二步:只在 queued 或 in_progress 时继续轮询
Responses retrieve 使用 GET /v1/responses/{response_id}。官方要求在 queued 或 in_progress 时继续查询;状态离开这两个值后就是终态,应停止轮询。
const RUNNING = new Set(["queued", "in_progress"]);
let current = await client.responses.retrieve(responseId);
while (RUNNING.has(current.status)) {
await sleep(nextDelay()); // 应用自己的退避与最大等待时间
current = await client.responses.retrieve(responseId);
}
switch (current.status) {
case "completed":
await saveCompleted(current.output_text, current.usage);
break;
case "failed":
case "cancelled":
case "incomplete":
await saveTerminalFailure(current);
break;
default:
await saveUnknownTerminalState(current);
}API Reference 当前列出的状态包括 completed、failed、in_progress、cancelled、queued 和 incomplete。不要写成“不是 in_progress 就成功”,也不要在多个 Vercel 实例中同时高频轮询同一个 response ID。
轮询间隔、超时和幂等怎样设计?
- 立即保存 ID:创建成功后先落库,再向浏览器返回;否则客户端刷新就可能丢失任务。
- 退避而非固定轰炸:官方示例的短间隔用于演示,不是生产 SLA。加入随机抖动、最大间隔和最大等待窗口。
- 单任务单调状态:用数据库锁、租约或队列避免多个实例重复查询与重复写结果。
- 终态幂等:以 response ID 为唯一键,completed Webhook 与轮询同时到达也只能完成一次。
- 区分 API 超时与任务失败:一次 retrieve 网络错误不等于后台任务失败;按 500/503 与连接错误规则有限重试。
- 保留审计字段:记录状态时间线、Request ID、用户、Project 和脱敏错误,不保存 API Key。
第三步:允许用户取消后台响应
后台任务仍在运行时,可调用 POST /v1/responses/{response_id}/cancel:
const cancelled = await client.responses.cancel(responseId);
await db.jobs.update(responseId, {
status: cancelled.status,
cancelledAt: new Date()
});官方说明只有用 background: true 创建的 Response 才能调用该取消接口;重复取消是幂等的,会返回最终 Response 对象。应用仍需先验证当前用户是否拥有该任务,避免任意 response ID 被他人取消。取消请求返回后,也应按返回的真实终态更新 UI,不能仅凭“按钮点击成功”假设模型已经停止。
不想轮询:用 Webhook 接收终态
可在 OpenAI Dashboard 配置 Webhook,订阅 response.completed、response.failed、response.cancelled 和 response.incomplete。Webhook 的 data 只给出 response ID 时,再由受信任后端 retrieve 详情。
- 读取原始请求体并验证 Webhook 签名,不信任未验签 JSON。
- 用
webhook-id去重;官方提醒少数情况下可能重复投递。 - 迅速返回 2xx,把 retrieve、数据库更新和通知放到后台队列。
- 不要依赖 3xx 跳转;官方不会跟随 Webhook 3xx。
- 处理轮询和 Webhook 竞态,以 response ID + 终态做幂等更新。
官方当前说明,未能及时返回成功 2xx 时会用指数退避重试,最长可持续 72 小时。因此“Webhook 收到两次”并不等于任务执行两次,但你的业务写入必须去重。
Background + Streaming:断线后从游标恢复
创建时同时设置 background: true 与 stream: true,既能立即消费事件,又允许原连接中断后重新拉取。保存 Response ID,并记录每个非 keepalive 存储事件的 sequence_number:
const stream = await client.responses.create({
model: "YOUR_MODEL",
input: "生成一份长报告。",
background: true,
stream: true
});
let responseId = null;
let cursor = null;
for await (const event of stream) {
if (event.type !== "keepalive") {
cursor = event.sequence_number;
}
if ("response" in event && event.response?.id) {
responseId = event.response.id;
}
await persistCursor({ responseId, cursor });
}
// 连接中断后,从最后一个已持久化事件之后恢复
const resumed = await client.responses.retrieve(responseId, {
stream: true,
starting_after: cursor
});
for await (const event of resumed) {
// 继续按 event.type 消费,并更新 cursor
}官方限制是:只有创建时已经设置 stream: true 的后台响应,才能启动新的恢复流。starting_after 使用的是已存储事件的 sequence number;keepalive 的连接计数不是恢复游标。官方还提示 Background Streaming 当前首 Token 时间可能高于同步流,因此它的主要目标是可恢复性,不应宣传成首屏加速。
数据保留与 ZDR:必须单独做合规评审
OpenAI 当前文档说明,ZDR Project 的后台请求以 store=false 运行,但为了异步执行和轮询,响应数据仍会临时存储到磁盘约 10 分钟。对 Modified Abuse Monitoring Project,只有显式 store=true 才会在轮询窗口后保留后台响应;省略 store 或设置 false,文档说明约 10 分钟后删除。
Background Mode 的异步执行存在官方说明的短暂磁盘存储例外。金融、医疗、法律或企业敏感数据应用,应让数据治理负责人核对合同、Project 控制和最新文档后再启用。
Background Mode 与 Batch API 的查重边界
| 问题 | Background Mode | Batch API |
|---|---|---|
| 任务单位 | 单个 Response | JSONL 中的大量请求 |
| 开始方式 | Responses create + background:true | 上传文件并创建 Batch |
| 完成通知 | 轮询或 response.* Webhook | 批次状态、Webhook 或结果文件 |
| 取消 | 按 response ID cancel | 按 batch ID 取消 |
| 适合交互 | 可做用户发起的长任务 | 更偏离线批处理 |
| 成本/限额 | 不要假设 background 自动折扣 | 按 Batch 官方价格与独立限额核对 |
常见错误与排查
| 现象 | 常见原因 | 处理 |
|---|---|---|
| 前端刷新后任务丢失 | response ID 只保存在组件状态 | 创建后服务端落库,并关联用户与 Project |
| 轮询永不停止 | 只判断 completed,忽略 failed/cancelled/incomplete | queued/in_progress 才继续,其他状态进入终态分支 |
| 大量 429 | 多实例固定高频轮询 | 加退避、抖动、租约与集中队列 |
| 恢复流重复文本 | 未持久化 sequence_number 或把 keepalive 当游标 | 按非 keepalive 存储事件更新 cursor,从 starting_after 续接 |
| 取消接口报错 | 原响应不是 background:true 或权限不匹配 | 确认创建参数、response ID 与 Project/用户所有权 |
| Webhook 重复写数据库 | 重复投递或轮询竞态 | webhook-id 去重,response ID 终态幂等更新 |
上线前检查清单
- 确认任务确实需要脱离请求连接,不把普通短回答全部改成 Background。
- 创建成功后先持久化 response ID、用户、Project 和初始状态。
- 只在 queued/in_progress 时轮询,所有终态都有独立 UI 与日志。
- 轮询具有退避、抖动、最大等待时间和多实例去重。
- 取消操作先鉴权,重复取消与重复终态写入均幂等。
- Webhook 验签、webhook-id 去重并快速返回 2xx。
- Background Streaming 保存非 keepalive sequence_number,并测试断线恢复。
- 合规团队已阅读 ZDR/store=false 的约 10 分钟临时磁盘存储说明。
- 用 Usage 与成本记录验证实际消耗,不把 Background 宣传成自动降价。
Background Mode 只负责让单个 Response 脱离原连接继续执行;端点配置、原始请求体验签、webhook-id 去重、快速 2xx 与重试处理请按 OpenAI Webhooks 验签与去重指南实现。
常见问题
OpenAI Background Mode 适合什么任务?
它适合单个 Responses API 请求可能持续较长时间、客户端连接容易超时或用户不必一直等待的任务。普通短回答继续使用同步请求;只需边生成边显示可用普通 SSE;大批量离线请求应单独评估 Batch API。
background true 后怎样知道任务完成?
保存创建请求返回的 response ID,使用 Responses retrieve 接口查询。只在状态为 queued 或 in_progress 时继续轮询;进入 completed、failed、cancelled 或 incomplete 等终态后停止,并按具体状态处理,不能把所有非运行状态都当成功。
Background Mode 可以取消吗?
可以通过 POST /v1/responses/{response_id}/cancel 或 SDK cancel 方法取消仍在执行的后台响应。官方说明只有使用 background:true 创建的响应才能调用取消;重复取消是幂等的,会返回最终 Response 对象。
Background Mode 能和 Streaming 一起用吗?
可以在创建时同时设置 background:true 与 stream:true。记录非 keepalive 事件的 sequence_number;连接中断后,用同一个 response ID、stream:true 和 starting_after 从已存储事件游标之后恢复。只有最初以 stream:true 创建的后台响应才能启动新的恢复流。
Background Mode 和 Batch API 有什么区别?
Background Mode 面向单个长时间 Responses 任务,按 response ID 轮询、取消或接收 Webhook;Batch API 面向大量离线请求,通过 JSONL 批次提交并在完成窗口内处理。两者的限额、成本和结果获取方式不同,不能互相替代。
Background Mode 是否兼容 Zero Data Retention?
官方当前说明,ZDR Project 的后台请求使用 store=false,但为了异步执行和轮询,响应数据仍会临时写入磁盘约 10 分钟。对数据驻留有严格要求的应用,应在启用前让合规和安全负责人核对这一例外。
应该一直每两秒轮询吗?
官方示例用短间隔演示,但生产系统不应把示例间隔当固定规则。应设置退避、最大等待时间和状态持久化,或使用 response.completed、failed、cancelled、incomplete Webhook;Webhook 也要验签、快速返回 2xx,并按 webhook-id 去重。
先把任务 ID、终态和恢复游标持久化
Background Mode 的可靠性来自状态管理,不是一个参数本身。上线前模拟浏览器刷新、网络断开、Webhook 重复和取消竞态。