想让 Responses 在正常生成时自动压缩,用服务端 compaction;想由应用明确控制压缩时机,用独立 compact 接口。服务端模式仍沿用原来的 input-array 或 previous_response_id 链;独立接口返回的是新的 compacted input window,不是可继续传的 response ID。无论哪种模式,都要把加密 compaction item 当作不可解析的状态载体。
官方资料(2026-10-30 核对):Compaction guide · Compact response API · Conversation state · WebSocket mode
先判断:Compaction 和哪些功能不是一回事?
| 机制 | 解决的问题 | 下一轮怎样继续 | 不要误解为 |
|---|---|---|---|
| 服务端 Compaction | 正常 Responses 生成中自动压缩增长的上下文 | 继续 input-array,或传最新 previous_response_id | 独立后台任务 |
| 独立 /responses/compact | 应用显式把完整窗口压缩成新的规范窗口 | 把 compact output 原样作为新 input,开启新链 | 返回新的 previous_response_id |
| previous_response_id | 让服务端关联上一条 Response 状态 | 传新输入与最新 response ID | 自动减少历史计费或自动压缩 |
| Background Mode | 单个长任务脱离原 HTTP 连接继续执行 | retrieve、Webhook 或恢复流 | 上下文压缩 |
| Streaming / WebSocket | 逐步接收事件或复用持久连接 | 按各自事件与状态规则继续 | 替代上下文管理 |
如果问题是“任务运行时间太长”,先看 Background Mode;如果问题是“每轮上下文越来越大”,才进入 Compaction。端点和状态基础可先读 Responses 与 Chat Completions 区别。
方案一:在 Responses create 中启用服务端 Compaction
在正常 responses.create 请求里加入 context_management。当渲染后的 Token 数跨过所设阈值,服务端在同一次生成流程中触发压缩,并在输出中带回加密 compaction item;不需要额外调用 /responses/compact。
import OpenAI from "openai";
import { toResponseInputItems } from "openai/lib/responses/ResponseInputItems";
const client = new OpenAI();
let input = [{
type: "message",
role: "user",
content: "开始一个长期代码审查任务。"
}];
async function runTurn() {
const response = await client.responses.create({
model: "YOUR_MODEL",
input,
store: false,
context_management: [{
type: "compaction",
compact_threshold: YOUR_EVALUATED_THRESHOLD
}]
});
// 输出中的 compaction item 也必须保留
input.push(...toResponseInputItems(response.output));
return response;
}compact_threshold 不是“越接近模型上限越好”的固定常数。请求里还可能包含工具定义、系统指令、本轮输入和模型输出预留;阈值要来自所用模型的真实上下文预算和应用评测。官方示例数值只是示例,不能直接当作所有模型或业务的推荐值。
服务端模式有两种续接方式,不要混成第三种
方式 A:无状态 input-array 链
应用自己持有 Items,每轮把上一轮输出转换为下一轮输入。服务端 compaction 触发后,最新 compaction item 会出现在输出里。官方说明,在已经把输出追加到历史数组后,可以删除“最新 compaction item 之前”的 Items,以减少请求体积和长尾延迟;最新 compaction item 及其后的 Items 必须保留。
input.push(...toResponseInputItems(response.output));
const latest = findLatestCompactionIndex(input);
if (latest >= 0) {
input = input.slice(latest);
}
input.push({
type: "message",
role: "user",
content: "继续下一步并运行测试。"
});删除动作只能发生在你明确找到最新 compaction item 后。不要靠字符数、消息条数或猜测位置删除,因为工具调用、reasoning item 与工具结果之间可能存在关联。
方式 B:previous_response_id 链
如果使用服务端状态链接,每轮只提交新的用户或工具 Items,并传最新 previous_response_id。官方明确提醒:这种模式不要再手动裁剪服务端链路。
const first = await client.responses.create({
model: "YOUR_MODEL",
input: "开始长期任务。",
store: true,
context_management: [{
type: "compaction",
compact_threshold: YOUR_EVALUATED_THRESHOLD
}]
});
const next = await client.responses.create({
model: "YOUR_MODEL",
previous_response_id: first.id,
input: "继续下一步。",
context_management: [{
type: "compaction",
compact_threshold: YOUR_EVALUATED_THRESHOLD
}]
});是否使用 store:true、能保存多久以及数据控制,应按 Project 和最新官方政策核对。本页只说明状态续接形状,不把存储设置当作合规结论。
方案二:显式调用 /responses/compact
独立 compact 接口适合应用自己持有完整上下文、需要在明确检查点压缩的场景。发送的输入窗口本身仍必须能放进所选模型的上下文窗口;已经超出窗口之后再调用并不能补救。
import OpenAI from "openai";
import { toResponseInputItems } from "openai/lib/responses/ResponseInputItems";
const client = new OpenAI();
const compacted = await client.responses.compact({
model: "YOUR_MODEL",
input: longInputItems
});
// compacted.output 是下一轮规范输入窗口
const nextInput = toResponseInputItems(compacted.output);
nextInput.push({
type: "message",
role: "user",
content: "基于已压缩状态继续任务。"
});
const response = await client.responses.create({
model: "YOUR_MODEL",
input: nextInput,
store: false
});不要把 compacted response 的 id 当作 previous_response_id;不要只保留加密 compaction item 而丢弃接口返回的其他 Items;不要对 compacted.output 再自行裁剪。官方把整个返回窗口定义为下一轮的规范输入,应原样延续。
与 Responses WebSocket 一起使用时怎样继续?
服务端 compaction 和独立 compact 的 WebSocket 续接方式不同:
- 服务端模式:在 WebSocket 的
response.create中配置context_management后,仍在同一 lane 传最新previous_response_id与本轮新 Items。 - 独立 compact:先通过 HTTP 调用
/responses/compact;然后在 WebSocket 发送新的response.create,把 compact output 加最新用户/工具 Items 作为input。 - 开启新链:独立 compact 后省略
previous_response_id或设为 null,因为 compact endpoint 返回的是输入窗口,不是上一条 Response。
完整的连接、lane、并发和断线边界见 Responses WebSocket 教程。Compaction 不能让已断开的连接自动恢复,也不能替代应用对工具副作用的幂等控制。
compact_threshold 怎样设计才不会过早或过晚?
- 先算硬预算:获取所用模型的当前上下文上限,再预留系统指令、工具 Schema、本轮输入和最大输出空间。
- 按真实渲染 Token 评测:不要只看字符数。图片、工具定义、reasoning、缓存和多语言文本都会影响实际 Token。
- 设置安全余量:阈值应低于可能触发 context overflow 的点,但过低会增加不必要的压缩频率。
- 保留任务检查点:长代码任务或 Agent 应把关键文件版本、工具结果 ID 和业务状态存进自己的数据库;不要把唯一事实只交给模型上下文。
- 分任务类型测试:长对话、代码修改、检索、工具链的最佳阈值可能不同,不要共用一个未经验证的全局常数。
如果需要在请求前更精确估算输入,可结合官方 token counting 能力;上线后再用 API Usage 对账真实输入输出,而不是凭页面字符长度推断费用。
质量、成本和延迟应该怎样验证?
Compaction 的目标是控制不断增长的上下文,但“压缩后一定更省钱、更快、质量不变”都不是可直接承诺的结论。建议用同一批长任务做 A/B:
| 指标 | 记录内容 | 失败信号 |
|---|---|---|
| 上下文 | 每轮 input/output Token、压缩触发轮次、请求体积 | 触发前已接近溢出,或压缩过于频繁 |
| 质量 | 任务成功率、事实保持、工具参数、测试通过率 | 忘记约束、重复工具、遗漏关键状态 |
| 延迟 | 首事件、总时长、P50/P95、压缩轮次长尾 | P95 上升或用户等待显著增加 |
| 成本 | 总输入、输出、缓存与压缩相关实际账单 | Token 降低但重试或错误成本上升 |
| 可靠性 | 4xx/5xx、链路恢复、幂等冲突 | 错误后丢上下文或重复副作用 |
工具调用、Reasoning 和业务状态要特别注意什么?
- 工具调用闭环:工具请求、
call_id和function_call_output必须保持关联;不要在自定义裁剪时拆开。工具实现见 Function Calling 教程。 - 不要解密或摘要 compaction item:它是服务端生成的加密状态对象,不是用户可见摘要,也不应进入日志分析正文。
- 业务事实外置:订单状态、文件版本、权限、已执行副作用等必须保存在应用数据库,Compaction 不是事务日志。
- 失败后不要盲目重放:先区分请求是否已被处理,再按幂等键和 5xx/超时规则重试。
- 可观测性脱敏:记录 response ID、Request ID、触发轮次、Token 和错误码;不要记录 API Key 或敏感原文。
ZDR、store=false 与安全边界
OpenAI 当前资料说明,独立 /responses/compact 是无状态且 ZDR-friendly;服务端 compaction 在 Responses 请求设置 store:false 时也可用于 ZDR 场景。加密 compaction item 仍要按敏感状态对象保护:只在受信任服务端传输和保存,限制访问,设置应用自己的保留周期,并在日志中脱敏。
“ZDR-friendly”不等于任何组织、模型、工具和外部数据源自动满足你的监管义务。启用前核对最新官方数据控制、Project 设置、合同和第三方工具的数据流。
常见错误与排查
| 现象 | 常见原因 | 处理 |
|---|---|---|
| 仍然出现 context overflow | 阈值过高、工具 Schema 或本轮输出预留不足 | 重新计算完整预算,并在压缩前预留安全余量 |
| 独立 compact 后 previous_response_not_found | 把 compacted response ID 当作 previous_response_id | 改用 compacted.output 作为新 input,并开启新链 |
| 压缩后工具状态缺失 | 自行删掉了返回窗口中的保留 Items | 独立 compact output 原样传递;检查工具状态是否另有持久化 |
| 服务端链路重复历史 | previous_response_id 模式又手动重放旧 Items | 只传新一轮 Items 与最新 response ID |
| 费用没有明显下降 | 压缩太频繁、任务太短或重试增加 | 查看真实 Usage,按任务类型调整而非承诺固定比例 |
| 4xx/5xx 后链路不确定 | 重试逻辑没有区分已处理与未处理 | 记录 Request ID、response ID 与幂等键,有限重试并保留检查点 |
上线前检查清单
- 确认问题确实是上下文增长,而不是 Background、Streaming 或 WebSocket 连接问题。
- 选择服务端自动压缩或独立 compact,并只实现对应的续接模式。
- 阈值来自当前模型上下文预算与真实任务评测,不复制示例常数。
- input-array、previous_response_id 与 standalone 新链的代码路径分别测试。
- 独立 compact output 原样保存和传递,不提取、解析或裁剪 compaction item。
- 工具副作用、权限、文件版本和关键业务事实已经外置持久化。
- 记录触发轮次、Token、P50/P95、质量、重试、错误率和实际成本。
- 模拟 context overflow、4xx、5xx、断线、重复请求和工具超时。
- ZDR、store=false、日志与数据保留经过安全和合规评审。
常见问题
OpenAI Responses Compaction 解决什么问题?
它用于长时间、多回合的 Responses API 工作流,在上下文逐渐变大时压缩旧状态,并把后续推理需要的关键信息放进加密 compaction item。它解决上下文增长问题,不是连接续传、后台执行或批处理机制。
Server-side compaction 和 /responses/compact 怎么选?
希望在正常 Responses 请求中自动触发时,用 context_management 和 compact_threshold;需要由应用决定压缩时机、显式持有完整输入窗口时,用独立 /responses/compact。前者随正常生成发生,后者返回一个新的规范输入窗口。
compact_threshold 应该设置多少?
官方没有给所有应用通用的唯一数值。阈值必须结合所用模型的上下文窗口、工具定义、输出预留、延迟和质量评测决定。先在测试流量记录压缩触发点、输入 Token、P95 延迟、任务成功率和成本,再逐步调整。
可以解析或修改 compaction item 吗?
不应该。官方把加密 compaction item 定义为不面向人类解释的状态载体。独立 compact 接口的 output 还是下一轮的规范窗口,通常还包含保留的旧 Items;应原样传给下一次 Responses 请求,不要只摘出 compaction item。
使用 previous_response_id 时还要手动删除旧 Items 吗?
启用服务端 compaction 并使用 previous_response_id 链接时,只发送新一轮输入并继续传最新 response ID,不要手动裁剪服务端链路。若改用独立 /responses/compact,则它返回的不是 response ID,需要把返回窗口作为新 input 并开启新链。
Compaction 一定会降低费用和延迟吗?
不能保证。压缩可减少后续携带的上下文,但压缩本身也会产生处理开销,质量、成本与延迟取决于模型、阈值、历史长度和工具链。上线前要用相同任务集比较总 Token、P50/P95 延迟、正确率和失败率。
Compaction 是否适合 Zero Data Retention?
OpenAI 当前说明,独立 /responses/compact 是无状态且 ZDR-friendly;服务端 compaction 在 Responses 请求设置 store=false 时也可用于 ZDR 场景。实际项目仍应核对组织控制、所用模型与最新数据政策,不能仅凭一个参数替代合规评审。