Assistants API 八月下线:别只换接口,五层状态迁移才是 Responses API 最容易翻车的地方
上个月,一个做知识库问答的朋友把一段报错甩给我:用户刚上传完合同,模型却像失忆一样重新追问文件在哪;更糟的是,客服在后台补了一条人工备注,下一轮回答又把它当成从没发生过。代码里没有明显异常,Assistants API 的 thread、run、file search 都还在,业务却开始出现一种最难排查的“半失忆”。
我们把问题追到最后,发现他为了赶进度,把业务会话、模型会话、工具执行状态全塞进了 thread。平时确实省事:新消息往 thread 里追加,创建 run,等它完成,再从 messages 里取答案。可一旦要做权限切换、审计、重放,或者换一套接口,这个看似整齐的抽象就会把所有边界缠在一起。
Assistants API 将在 2026 年 8 月 26 日关闭。真正需要迁走的并不是几个 endpoint,也不是把 assistant_id 改成一个新的模型名。麻烦在于:过去由 thread 和 run 悄悄代管的状态,现在要被团队重新看见、重新命名、重新放回自己的系统里。
这篇不写“十分钟迁移教程”。那类教程最容易让人误判工作量:请求能返回 200,不代表应用已经迁完。下面我按真实工程里最容易漏掉的五层状态来拆:对话、指令、工具、文件、运行记录。最后给一套能逐步替换、又不拿真实用户当测试数据的迁移骨架。
别把旧接口的对象名原样搬过去
Assistants API 的诱惑,在于它替开发者打包了很多东西。你创建一个 assistant,配置指令、模型和工具;为每个用户建立 thread;把消息塞进去;创建 run;平台再帮你推进模型调用、工具调用和结果写回。原型阶段,这比自己维护一套状态机轻松得多。
可它也会制造一个错觉:好像“对话”天然就应该归模型供应商保存。实际上,业务系统里至少同时存在三种不同的连续性。
- 用户连续性:这个人是谁,权限是什么,历史订单能看哪些。
- 任务连续性:这次是在改合同、查库存,还是在跑一次数据修复。
- 模型连续性:上一轮模型看过什么、调用过什么、准备接着推理什么。
旧架构里,这三件事常常被一个 thread_id 勉强绑住。Responses API 的迁移价值,不只是换成一个更现代的调用形式,而是逼着你把它们拆开:用户与任务属于你的数据库;模型上下文只是一段可以续接、也可以丢弃的计算状态。
这一步听起来像架构洁癖,到了故障现场才知道它有多实际。比如某个用户从普通员工升成管理员,你不应该因为一个旧 thread 还在,就让新权限自动继承旧上下文里的敏感检索结果。再比如客服希望重放一条回答,真正需要保存的是“当时的输入、工具返回、模型版本、提示词版本”,不是笼统地记一句“这个 thread 跑过”。
迁移的第一条原则:先把你真正拥有的状态列出来,再决定哪些交给 API 保存。
先做资产清单,别先改 SDK
我建议在写第一行新代码之前,先把线上所有 assistant 拉成一张清单。不要只抄 ID。每一行至少标出它服务哪个产品、日均请求、是否带文件检索、有哪些函数、函数里是否存在写操作、是否保存过 thread、是否和外部工作流相连。许多团队到了这一步才发现:看似一个聊天机器人,背后其实混着客服问答、内部报表、工单创建和付款提醒四种风险完全不同的应用。
然后再列 thread 的用途。有人拿它做用户长期聊天;有人一张订单一个 thread;有人甚至把一次批处理任务也塞进 thread。后两种在迁移时最容易出错,因为业务任务结束后,模型状态其实不一定要继续保留。把所有 thread 一股脑映射为 previous_response_id,只会把旧系统的混乱原封不动带过去。
清单里还要留一列叫“可删除性”。用户注销后,哪些业务记录需要删除,哪些只需要脱敏,哪些模型响应还在供应商侧存储期内,团队必须在迁移前对齐。过去不清楚并不代表不存在;只是旧接口把它藏得更深。迁移是补齐数据边界的好时机,而不是把历史债务压缩成一段兼容代码。
这份清单不会直接让产品更酷,却会决定你是否能在八月前完成。没有它,工程师只能在每个报错出现时临时问“这个 assistant 是谁在用”,时间会被沟通耗掉,真正的代码反而不是瓶颈。
第一层:对话状态不要再只剩一个 thread_id
Responses API 支持用 previous_response_id 接续上下文。这个字段很方便,但不要把它误当成业务会话主键。更稳妥的做法,是在自己的表里维护一个“业务会话 → 最近模型响应”的映射,同时保留自己的消息副本和版本信息。
一个最小的会话记录,至少应有这些字段:conversation_id、user_id、task_type、latest_response_id、prompt_version、policy_version、updated_at。前面三个解决业务归属,后四个是以后排错、灰度和审计时能救命的东西。
很多人迁移时会直接把每一轮完整聊天记录再次拼进 input。小流量时没问题,量一大就会出现两笔成本:token 一轮轮膨胀,提示词里的旧规则也越来越难清理。用 previous_response_id 能让模型保持连续,但它不替你做权限校验,也不替你保证历史指令仍然适用。
这里有个很容易被忽略的细节:当你用 previous_response_id 续接时,上一轮的 instructions 不会自动当作永久系统规则继承。换句话说,团队必须明确选择:每轮都带上当前的开发者指令,还是把规则版本固定在业务会话上。我的建议是前者——每轮携带当前生效的关键规则,并把版本写进日志。规则变了,下一轮才有机会真的变。
这不是多写几行代码,而是把“模型记得什么”从黑盒改成了可管理的资产。你可以在用户注销时删除业务会话映射;可以在发现提示词污染时从一个干净的响应重新开始;也可以把高风险任务切到不保存上下文的短会话,而不用改动整个产品。
更重要的是,业务会话和模型连续性应该允许一对多。一个客服工单可能先经过检索问答,再进入人工补充,最后由模型生成回复。业务侧它始终是同一张工单,模型侧却可以因为权限变更、上下文过长或提示词升级而重新起一段 response 链。把这层关系设计清楚,后面换模型、做 A/B 对照都不会牵一发动全身。
第二层:指令是产品配置,不是散落在代码里的长字符串
迁移中最容易被低估的,是 instructions。旧 assistant 往往在创建时绑了一段很长的系统提示词,里面既有语气、格式,也混着合规边界、工具使用条件和业务例外。新调用如果只迁一个模型名和用户输入,表面当然能出答案,质量却会像突然换了一个客服。
先把指令拆成三类:稳定的角色约束、随用户权限变化的业务规则、随任务变化的上下文。稳定规则可以按版本管理;权限规则应该由服务端实时拼装;任务上下文只在本次调用传入。这样某项政策更新时,你不会因为还有五十个历史 assistant 存着旧指令而漏改。
不要把提示词版本号只写在 Git 提交信息里。每次请求把 prompt_version 放进结构化日志,才有可能回答“为什么上周同一个问题还能查到,今天查不到”。很多所谓模型波动,后来追出来其实只是一个隐藏分支改了工具描述,或者权限提示词没有跟着产品配置更新。
模型输出可以有随机性,产品规则不能靠回忆。这就是把指令从一段文本升级为可发布配置的原因。
先加一层迁移适配,不要把新旧逻辑搅在同一处
实际落地时,我会在业务服务和 SDK 之间放一个很薄的适配层。它不负责重新发明 Agent 框架,只做四件事:把业务会话解析成当前模型上下文;统一写入调用记录;把工具定义交给现有业务服务;把最终 response 映射回产品需要的消息格式。旧 Assistants 路径和新 Responses 路径都经过这层,产品页面、权限系统和工单系统就不用同时改两次。
这样做的价值,是让迁移变成可比较的替换,而不是大规模重构。相同的用户问题可以先经过两条实现,适配层记录两边的耗时、工具调用和引用文件。差异出现时,工程师不必在前端、数据库、提示词、SDK 四个地方同时猜;先看适配层的统一事件,就能知道是输入被改了、工具结果不同,还是模型输出不同。
适配层还应该明确“谁有资格续接上下文”。例如用户换了租户、后台客服转交了工单、权限组被收紧,业务服务可以主动不给 previous_response_id,重新创建干净的模型链,而不是把历史记忆默认带到新的边界里。这种主动切断,在旧 thread 模型里往往被忽略,在新接口下反而更容易成为可读的业务规则。

上图表达的不是“旧接口和新接口谁更高级”,而是责任边界的变化:旧的 assistant、thread、run 把连续对话和执行编排包进一个黑箱;新的 response loop 要求你把用户、权限、工具和日志放在外圈。刚开始代码更多,出问题时却少得多。
第三层:工具调用从 run 生命周期,回到你的业务事务
Assistants API 的 run 有一个很强的心理暗示:模型开始运行、卡在 requires_action、工具提交输出、最终 completed,流程看上去像平台已经替你托管了。迁移后,函数调用会更直接地出现在 response 输出项里。表面上少了一个对象,实际上是把工具调度权交回给了你。
这恰恰是应该接住的部分。订单查询、退款、发券、写库这类工具,不能只因为模型给了一个合法 JSON 就立即执行。它们本质上都是业务事务,应该经过参数校验、权限判定、幂等键、审计记录,再把结果作为 function_call_output 送回模型。
下面这段骨架省略了数据库实现,但把边界留清楚了。重点不在 SDK 写法,而在于 工具调用和业务执行之间必须有你自己的服务层。
import OpenAI from "openai";
const client = new OpenAI();
const tools = [{
type: "function",
name: "lookup_order",
description: "查询当前用户有权限查看的订单",
parameters: {
type: "object",
properties: { order_no: { type: "string" } },
required: ["order_no"],
additionalProperties: false
}
}];
async function ask(conversation, userText) {
const response = await client.responses.create({
model: "gpt-5",
instructions: currentInstructions(conversation.policy_version),
previous_response_id: conversation.latest_response_id || undefined,
input: userText,
tools
});
const calls = response.output.filter(x => x.type === "function_call");
const outputs = [];
for (const call of calls) {
const args = JSON.parse(call.arguments);
const result = await lookupOrderWithPolicy({
userId: conversation.user_id,
orderNo: args.order_no,
idempotencyKey: call.call_id
});
outputs.push({ type: "function_call_output", call_id: call.call_id,
output: JSON.stringify(result) });
}
if (outputs.length === 0) return response;
return client.responses.create({
model: "gpt-5", previous_response_id: response.id,
instructions: currentInstructions(conversation.policy_version),
input: outputs, tools
});
}
这个循环里,call.call_id 很适合作为一次工具执行的关联标识,但不要把它当成天然的数据库幂等键就结束了。真正写库的操作还应当绑定业务侧的请求 ID:同一个用户连续点两次“确认退款”,模型即使生成两个调用,也应该由业务规则决定能不能发生两次。
很多迁移事故都发生在这里。测试环境只跑了查询工具,于是大家以为 function calling 已经通了;上线后第一次遇到扣款、库存锁定、邮件发送,才发现 run 时代隐藏的重试语义不见了,业务服务也没有自己的去重和审计。
工具 schema 也不要只放在一个常量文件里。它和数据库字段、接口权限一样会演化:参数改名、枚举新增、描述调整都会影响模型选择。为 schema 加版本号,在日志中保留调用时的版本;当你需要比较新旧模型的工具选择质量时,才能避免把 schema 变化误判为模型退化。
第四层:文件与检索不是附件,而是一套可追溯的数据产品
知识库应用最容易在迁移时丢东西。旧项目往往只有一个 assistant 配置,里面挂着 file search;文件是谁上传的、何时生效、覆盖了哪个版本、被哪次回答引用过,信息散在后台页面、thread 附件和业务库的不同角落。
迁移前先做一次文件资产盘点,不要急着上传。按“原始文件、解析版本、向量库归属、业务权限、有效期”五列导出清单。文件本身和可检索性不是一回事:同一份 PDF 可能仍要保留原件,但旧版本不应再进入新的向量检索;同一个向量库也不能默认对所有租户开放。
Responses API 可以通过 file_search 连接向量库,但你仍然需要一张自己的映射表:tenant_id、knowledge_base_id、vector_store_id、document_version、active。模型请求只拿到经过业务层筛过的 vector store。这样用户问“合同第七条”时,检索范围不是由模型猜,而是由产品权限确定。
还有一个常被忽视的指标:命中并不等于正确。迁移期间要抽样保存检索结果的文件 ID、片段、相似度或引用信息,再和最终回答绑定。以后业务方说“AI 编的”,工程师才能判断问题出在文件没同步、召回错了,还是模型误读了正确片段。
同步策略也该单独验收。给文件上传一套可见状态:原件已接收、解析完成、索引完成、权限生效、旧版本下线。别让“上传成功”成为唯一状态;它只说明二进制到了某个地方,不说明用户已经能在正确权限下被检索到。实际产品里,最危险的不是文件不存在,而是文件半可用时模型已经开始回答。
文件迁移最怕“数据搬过去了,所以功能等价”的幻觉。真正等价的判断是:同一个用户、同一个权限、同一个问题,在新旧路径上是否检到同一批有效材料,并给出可接受的答案。
第五层:run 消失后,运行记录反而要更完整
以前打开一个 run,就能看到 queued、in_progress、requires_action、completed 或 failed。很多团队把这当成了观测能力。其实那只是平台层状态;一旦请求跨越网关、队列、多个业务工具和人工审批,你需要的是一条自己的端到端轨迹。
建议为每次用户任务生成 task_id,再让它贯穿 API 调用、工具执行和业务日志。每一轮至少保存:请求时间、模型与参数、输入摘要、response ID、工具名、工具参数摘要、工具耗时、工具结果摘要、最终输出、错误类别。敏感原文不一定要全部落日志,但关联关系不能没有。
这样做的直接收益,是你不必靠猜来回答三个问题:这次回答慢,到底慢在模型还是慢在库存系统?同一条工具为什么执行两次?某个答案引用的是今天更新的制度还是三个月前的旧文件?如果这些问题只能去供应商控制台和自家日志各翻一遍,迁移根本没有完成。
Responses API 本身提供了更直接的输出项和追踪能力,但不要把“看得到 response ID”误当成可运营。运营需要能按用户、任务、版本和工具聚合;工程需要能从一个投诉反查到一轮具体调用;安全团队需要知道一条敏感数据究竟经过了哪个远程工具。这些索引只能由应用自己建立。
为了避免日志变成另一座垃圾山,我通常会把“可检索的事件字段”和“受控保存的原始内容”分开。前者用于日常看板和告警,例如耗时、模型、工具、状态码;后者只在有权限的排障场景读取。这样既不会因为完全不留证据而无法复盘,也不会因为把所有输入输出无限堆积而制造新的数据治理问题。
一套不打断业务的迁移顺序
真正迁的时候,我更推荐按能力拆,而不是找一个周末把所有 endpoint 替换掉。顺序的核心是:先让新路径看见旧路径的真实输入和结果,再让少量低风险任务经过新路径,最后才把旧对象退场。
- 先盘点:列出所有 assistant、thread、工具、文件库与调用量。
- 先建表:补齐业务会话、响应映射、工具审计和文件映射。
- 先做纯问答:不带写操作,让新旧路径对同一输入做对照。
- 再接查询工具:把权限、参数校验和幂等放进业务服务。
- 最后接写操作:退款、发信、写库必须单独验收每条事务。
对照时别只看“最终文案像不像”。至少要同时比较四件事:首 token 和总耗时、工具调用次数、检索命中的文件版本、业务错误率。新模型可能回答更好,却多调了两次高成本工具;新检索可能更快,却漏掉了被权限正确允许的附件。没有这些数据,所谓迁移成功只是一种感觉。
验证样本也要从真实问题里来。挑二十条生产中出现过的典型请求:简单问答、带文件检索、多轮追问、工具查询、权限边界、异常输入各占一些。把期望结果写成可观察条件,而不是“回答看起来不错”。例如订单号不存在时不能编造;越权用户不能看到订单字段;文件更新后旧条款不能再被引用;一个写操作在网络抖动下不能重复提交。
这批样本以后就是你的回归集。模型升级、工具 schema 改动、提示词调整都能再跑一遍。没有回归集,团队每次发版都只能靠人工在聊天框里试两句,某个边缘任务悄悄坏掉也没人知道。
还有一个工程上的小建议:给每个提示词、工具 schema、知识库配置都加版本号。Responses API 让组合方式更灵活,也意味着上线后变动更频繁。没有版本号,三周后你会面对一条无法复现的投诉:同一句问题,当时到底用了哪个系统提示词、哪版函数参数、哪组文件?
我见过最省时间的团队,不是写代码最快的团队,而是一开始就承认“迁移的是一个运行中的产品,不是一段 SDK 示例”。他们先把边界画清楚,后来加一个工具、换一个模型、收紧一条权限,都不用再去猜 thread 里藏了什么历史。
真正该带走的,不是旧对象,而是控制权
Assistants API 的退场并不意味着过去的设计一无是处。它让很多团队快速验证了 Agent 产品的可能性,也替早期项目省掉了不少状态管理工作。但当应用开始接入真实用户、真实文件和真实事务,状态总会回到你手里——只是早一点正视和晚一点被事故逼着正视的区别。
所以这次迁移最值得保留的成果,不是一个新的 responses.create 调用,而是一张能说清楚责任的架构图:用户状态在业务库,模型上下文有明确映射,工具执行走业务事务,文件检索受权限约束,运行记录可以完整追溯。
接口会继续更新,模型会继续换代。可只要这五件事还在自己手上,下一次变更就不会再是一场把所有状态塞进黑盒、然后祈祷它们自己长好的迁移。
资料:OpenAI Assistants API deep dive、OpenAI Responses API quickstart。
更多推荐



所有评论(0)