短链路或一次工作流优先手动 Items;只想少传历史请求体可用 previous_response_id;需要一个跨设备、后台任务或多次登录都能复用的会话 ID,再用 Conversations API。不要把 conversation 和 previous_response_id 放进同一次请求;后者不会继承上一轮顶层 instructions,也不会免除历史输入 Token 计费。
官方资料(2026-10-30 核对):Conversation state · Responses migration · Conversations API · Data controls
三种方案先放在一张表里判断
| 方案 | 状态由谁持有 | 适合场景 | 主要代价与边界 |
|---|---|---|---|
| 手动回传 Items | 你的应用和数据库 | 需要审计、裁剪、迁移,或严格控制发送内容 | 要保存完整消息、reasoning、工具调用与输出之间的关系 |
| previous_response_id | OpenAI Response 链 | 单线程多轮问答、工具链,想少传历史请求体 | 顶层 instructions 每轮重发;历史输入仍计费;Response 默认保留规则需核对 |
| Conversations API | 持久 conversation 对象 | 跨会话、设备、作业或 Worker 继续同一状态 | 保留到删除,不符合当前 ZDR;应用必须做归属、权限和删除 |
这三种方式解决的是“下一轮从哪里取得上下文”。如果问题是上下文越来越大,应另看 Responses Compaction;如果问题是单个请求运行太久,则看 Background Mode。
方案一:应用手动管理历史 Items
手动模式最透明:每次把上一轮 response.output 转为下一轮可接受的 input Items,再加入本轮用户输入。应用可以在发送前审计、过滤或压缩上下文,也能把供应商状态迁移到自己的数据库。
import OpenAI from "openai";
import { toResponseInputItems } from "openai/lib/responses/ResponseInputItems";
const client = new OpenAI();
let input = [{
type: "message",
role: "user",
content: "分析这个故障并给出第一步。"
}];
const first = await client.responses.create({
model: "YOUR_MODEL",
instructions: "你是生产故障分析助手。",
input,
store: false
});
input.push(...toResponseInputItems(first.output));
input.push({
type: "message",
role: "user",
content: "继续第二步,并保留已确认的证据。"
});
const second = await client.responses.create({
model: "YOUR_MODEL",
instructions: "你是生产故障分析助手。",
input,
store: false
});不要只保存最终文本。Responses 输出可能包含 message、reasoning、function_call 和其他 typed Items;工具调用还要保持 call_id 与 function_call_output 配对。完整工具循环见 Function Calling 教程。
手动模式什么时候更合适?
- 你需要在数据库中保留可审计的规范上下文,而不是只保存一个远端 ID。
- 你要根据租户、任务或敏感级别决定哪些 Items 可以进入下一轮。
- 你需要显式压缩、迁移提供商,或在离线测试中重放完全相同的输入。
- 你的数据策略不能接受 Conversation 对象“保留到删除”的服务端持久化。
代价是应用要正确处理 Items 的顺序和依赖。不要按“最近 N 条可见消息”粗暴裁剪,因为一条工具结果、reasoning item 或调用 ID 可能依赖更早的 Item。
方案二:用 previous_response_id 串联 Response
previous_response_id 让下一次请求引用上一条 Response,适合单条链路或从某个 Response 分叉。应用仍要保存最新 response ID,但不必在每个请求里重传全部历史 Items。
const first = await client.responses.create({
model: "YOUR_MODEL",
instructions: "只依据已确认信息回答。",
input: "列出第一步。",
store: true
});
const next = await client.responses.create({
model: "YOUR_MODEL",
instructions: "只依据已确认信息回答。", // 每轮重发
previous_response_id: first.id,
input: "继续第二步。",
store: true
});第一,上一条 Response 的顶层 instructions 不会自动继承,因此稳定指令要每轮重发。第二,官方当前说明链中的既往输入 Token 仍按输入 Token 计费;少传请求体不等于历史上下文免费。
Response 对象默认会保存 30 天,创建时可用 store:false 关闭这一默认行为,但实际可用性还受组织数据控制、工具和所选功能约束。需要跨很长时间继续时,不要假设任意旧 response ID 永久可取;要么在应用保存规范 Items,要么评估 Conversations API。
如何处理分支与失败?
从同一个旧 response ID 发起不同后续请求会形成分支。应用数据库应记录 user_id、内部 thread_id、当前 response_id 和父 ID,避免两个并发请求同时覆盖“最新 ID”。发生 5xx 或超时时先核对请求是否已成功处理,再决定重试;具体证据和退避见 API 500/503 与超时排查。
方案三:用 Conversations API 建立持久会话
Conversation 是拥有独立 durable ID 的持久对象,可以跨浏览器会话、设备、Worker 或后台任务继续。它保存的 Items 不只有消息,也可包含工具调用、工具输出等状态。
const conversation = await client.conversations.create({
metadata: {
app_user_id: "user_123",
purpose: "support_case"
}
});
const first = await client.responses.create({
model: "YOUR_MODEL",
conversation: conversation.id,
instructions: "回答前先检查已有会话状态。",
input: "记录订单故障的第一条证据。"
});
const next = await client.responses.create({
model: "YOUR_MODEL",
conversation: conversation.id,
instructions: "回答前先检查已有会话状态。",
input: "现在继续排查。"
});Responses create 中的 conversation 会把该会话已有 Items 放在本轮 input 之前,并把本轮输入与生成结果加入会话。API Reference 明确说明,conversation 与 previous_response_id 不能同时使用;迁移时应选一个主状态键,而不是叠加两套链路。
应用数据库仍然不可省略
持久 Conversation 不替代你的用户、权限和业务数据库。至少保存:
- 内部用户或租户 ID 与 conversation ID 的映射;
- 创建人、可访问角色、用途、创建与最后活动时间;
- 业务对象 ID,例如工单、项目或任务;
- 删除、导出和审计状态;
- 当前模型、关键 instructions 版本与应用发布版本。
每次读取、列出 Items、追加输入或删除前,都要在服务端验证该 conversation 属于当前用户或租户。API Key 只放在服务端环境变量,不把 Conversation API 直接暴露给浏览器。
Conversation 的读取、分页与删除怎么做?
Conversations API 提供创建、读取、更新和删除对象的操作,也提供 Items 的创建、读取、列出和删除。列表接口支持分页和顺序参数;不要假设一次响应包含无限历史。
// 列出一页 Items
const page = await client.conversations.items.list(conversation.id, {
limit: 20,
order: "asc"
});
// 删除单个 Item
await client.conversations.items.delete(
conversation.id,
"ITEM_ID"
);
// 用户要求删除整个会话
await client.conversations.delete(conversation.id);删除某个历史 Item 不会改变已经生成并交付给用户的旧答案;它主要影响后续可用状态与保留记录。删除整个 Conversation 后,应用也要同步清除本地映射、缓存和搜索索引,并把失败重试设计为幂等操作。
数据保留、ZDR 与隐私边界
| 对象或方式 | 官方当前保留说明 | 应用责任 |
|---|---|---|
| Response 对象 | 默认保存 30 天;可在适用场景设置 store:false | 核对组织控制、工具与功能例外,不把默认值写成合同承诺 |
| Conversation 与 Items | 不受 Response 30 天 TTL 限制,保留到删除 | 实现归属、权限、用户删除和保留周期任务 |
| 手动 Items | 由你发送并按所用 API 的数据控制处理 | 控制自有数据库、日志、备份和传输内容 |
OpenAI 当前数据控制表把 /v1/conversations 和 conversation Items 标为保留到删除,并且不符合 Zero Data Retention。若项目有严格 ZDR 要求,不要先创建持久 Conversation 再试图用 store:false 抵消;应选择符合政策的无状态路径并让安全、法务或合规负责人复核最新文档。
Conversation State 与 Compaction 怎样配合?
状态持久化和上下文窗口管理是两层问题。Conversation 可以不断累积 Items,但模型每次仍受上下文窗口限制;previous_response_id 也不会自动让无限历史免费或无限可见。
- 先确定状态所有权:手动 Items、Response 链或 Conversation ID。
- 再控制窗口增长:按任务评测是否启用服务端 compaction、独立 compact,或应用自定义摘要与检查点。
- 业务事实外置:订单、权限、文件版本和已执行副作用保存在业务数据库,不能只依赖模型上下文。
- 监测真实用量:记录每轮 input/output Token、延迟、错误和质量,并用 API Usage 对账。
Compaction 的两种模式、阈值和续接规则见 Responses Compaction 教程。
生产架构建议:状态键、权限和并发
| 风险 | 错误做法 | 建议 |
|---|---|---|
| 越权读取 | 拿到 conversation ID 就允许访问 | 服务端按租户和用户归属校验,每次操作都授权 |
| 并发覆盖 | 两个请求同时写同一“最新 Response” | 用版本号、队列或乐观锁记录父 ID 和当前状态 |
| 指令漂移 | 以为 previous_response_id 自动带上 instructions | 保存指令版本并在每轮显式发送 |
| 无限增长 | 只持久化,不设置窗口或保留策略 | 同时设计 Compaction、删除期限和业务检查点 |
| 不可审计 | 只存最终文本,不存 ID 和工具状态 | 记录 response/conversation/item ID、Request ID、工具调用与应用版本 |
| 重复副作用 | 超时后直接重放工具请求 | 工具使用幂等键,重试前检查结果和调用状态 |
常见错误与排查
- 400:conversation 与 previous_response_id 同时出现:保留一个主状态方案,移除另一个字段。
- 下一轮行为像“忘了系统指令”:检查是否只传了 previous_response_id 而没有重发顶层 instructions。
- Conversation 读不到:核对 Project、API Key 权限、ID、环境,以及对象是否已删除;不要跨项目猜测 ID。
- 历史越来越贵:previous_response_id 不免历史输入计费;检查真实 Usage,并单独设计窗口与 Compaction。
- 列表缺少早期 Items:检查分页游标、limit 和 order,不要把一页结果当完整历史。
- 用户删除后仍在本地出现:清理应用数据库映射、缓存、向量索引和备份流程,不只调用远端 delete。
上线前检查清单
- 明确一个主状态方案,并记录为什么不用另外两种。
- previous_response_id 与 conversation 不会同时发送。
- 顶层 instructions 在需要时每轮显式传递,并有版本记录。
- 工具调用、reasoning 与工具输出 Items 的关联不会在裁剪时被拆开。
- Conversation ID 只由服务端读取,且每次操作都验证用户和租户归属。
- 实现 Items 分页、会话删除、缓存清理和失败幂等。
- 验证 Response 默认 30 天与 Conversation 保留到删除的差异。
- 严格 ZDR 项目没有使用当前不符合 ZDR 的 Conversations API。
- 监控 Token、P50/P95、错误率、任务成功率和重复副作用。
- 对并发写、旧 ID、超时、5xx、权限错误和删除后的请求做回归测试。
还要记录文件所有者、purpose、到期和删除状态;完整生命周期见 OpenAI Files API 指南。
Conversation 保存对话 Items,Vector Store 保存可检索文件索引;后者流程见 OpenAI File Search 指南。
常见问题
OpenAI Conversation State 有哪三种管理方式?
可以手动把历史 Items 放回下一次 input,也可以用 previous_response_id 让 OpenAI 串联上一条 Response,或用 Conversations API 创建可跨会话、设备与任务复用的持久 conversation。选择依据是状态由谁持有、是否需要长期标识,以及数据保留要求。
previous_response_id 和 Conversations API 应该怎么选?
只需沿单条 Response 链继续时,previous_response_id 更轻量;需要一个能跨会话、设备或后台任务复用,并可单独读取、更新或删除的持久对象时,用 Conversations API。两者不能在同一次 Responses create 请求中同时使用。
使用 previous_response_id 后还要重复发送 instructions 吗?
要。OpenAI 当前文档明确说明,上一条 Response 的顶层 instructions 不会自动带到下一条,因此稳定的系统或开发者指令应在每次请求中重新发送。
previous_response_id 会减少历史 Token 计费吗?
不会自动免除。OpenAI 当前说明,即使用 previous_response_id 串联响应,链中既往输入 Token 仍作为输入 Token 计费。它简化状态传递,不等于免费上下文或自动压缩。
Conversation 对象会在 30 天后自动删除吗?
不会按 Response 对象默认的 30 天规则自动过期。官方当前说明,Conversation 对象及其中 Items 会一直保留到删除;因此应用必须提供权限校验、删除路径和自己的数据保留策略。
Conversations API 适合 Zero Data Retention 项目吗?
按 OpenAI 当前数据控制表,/v1/conversations 及其 Items 会保留到删除,且不符合 Zero Data Retention。严格 ZDR 场景应改用符合组织控制的无状态方案,并由安全或合规负责人核对最新政策。
Conversation State 和 Compaction 是一回事吗?
不是。Conversation State 决定多轮上下文怎样保存和续接;Compaction 处理上下文不断增长、接近窗口上限的问题。长会话通常既要选状态方案,也要单独设计压缩、截断或业务检查点。
Session、Conversation 与 Response 事件不同于 Responses 的 previous_response_id;60 分钟上限、语音截断与重连见 Realtime API 教程。