1. 旧 Agent 应用为什么在 Assistants API 下线后直接 404
如果你的项目里还留着client.beta.assistants.create、client.beta.threads.create这类调用,现在最该关心的不是模型答得准不准,而是请求发出去根本没有服务接。OpenAI 的 Assistants API 已经走完 sunset 流程,旧接口返回 404 不是限流、不是鉴权过期,而是对象本身不存在了。这个区别很重要:限流你会重试,鉴权过期你会换 Key,而 404 意味着你整套基于 Assistant/Thread/Run 的编排逻辑需要换一套对象模型。
受影响最典型的是四类应用:客服机器人、知识库问答、长会话助手、带工具调用的 Agent。它们过去依赖 Assistants API 把「配置 + 会话 + 执行过程」打包成持久对象,代码写得比较省心——建一个 Assistant 存好 instructions 和工具,建一个 Thread 存消息,然后 create run 再 poll 状态。现在这套持久对象没了,官方迁移目标是 Responses API,但这不是改个 endpoint 就能恢复的事。
我见过最常见的误判是:以为把https://api.openai.com/v1/threads换成/v1/responses就完事。结果请求格式对不上、会话状态接不回来、工具调用循环直接断掉。原因在于两边的抽象层级不同。旧模型里,Assistant 是服务端持久配置对象,Thread 是服务端持久消息容器,Run 是在 Thread 上执行的一次任务,状态由服务端维护。新模型里,配置进入可版本化的 Prompt,会话变成 Item 流,Response 接收输入返回输出,而工具调用循环、历史裁剪、重试这些编排职责,更明确地落到了应用侧。
换句话说,迁移的本质是「把服务端帮你管的状态,搬回你自己的代码里管」。这也是为什么本文不叫「改 URL 教程」,而叫重建。下面我会先讲清楚对象映射,再给出把 endpoint 和鉴权切到 TaoToken 的可复制配置,最后用一次真实对话请求验证迁移是否成功。整套流程你可以跟着做,不需要推倒重来,但需要按对象逐个替换。
2. 迁移前先把 TaoToken 接入配置准备好
在动业务代码之前,先把请求出口和鉴权准备好,这样后面调试 Responses API 时不会把「迁移问题」和「网络/鉴权问题」混在一起。TaoToken 的接入方式兼容 OpenAI SDK 的调用习惯,你只需要改 Base URL 和 API Key 两个地方,模型 ID 按你实际要用的填。
先拿 Key。打开 API Keys 页面创建一个密钥,建议按项目或环境分开建,方便后面出问题能单独吊销:
https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api_keys&utm_campaign=rewrite创建后把 Key 存到环境变量,不要硬编码进仓库。我习惯用.env加python-dotenv或系统的环境变量管理:
export TAOTOKEN_API_KEY="sk-你的密钥" export TAOTOKEN_BASE_URL="https://taotoken.net/api"注意 Base URL 这里写的是https://taotoken.net/api,不带任何查询参数。SDK 会自动在它后面拼/v1/responses这类路径,所以你不要自己再加/v1,否则会变成/api/v1/v1/responses这种重复路径,直接 404。
如果你用的是 OpenAI 官方 SDK,初始化客户端时这样写:
import os from openai import OpenAI client = OpenAI( api_key=os.environ["TAOTOKEN_API_KEY"], base_url=os.environ["TAOTOKEN_BASE_URL"], )Node/TypeScript 项目同理:
import OpenAI from "openai"; const client = new OpenAI({ apiKey: process.env.TAOTOKEN_API_KEY, baseURL: process.env.TAOTOKEN_BASE_URL, });这里有个容易踩的点:旧代码里如果用的是openai.beta命名空间,迁移后要改成普通客户端调用。beta.assistants、beta.threads、beta.runs这些入口在新对象模型下不再对应,继续用只会报属性不存在。先把客户端初始化干净,再往下写 Responses 调用。
模型 ID 方面,Responses API 用的是对话模型,不是 Assistant 那种「配置对象里带模型」的写法。你在请求里直接指定model字段即可。具体可用模型以文档为准,接入文档在这里:
https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite配置阶段建议先跑一个最小连通性测试,确认 Key 和 Base URL 没问题,再进入对象迁移。这样后面如果报错,你能快速判断是配置层还是代码层的问题。
3. 可复制的 Responses API 配置与对象映射改造
这一节是迁移的核心。我先把四组对象映射摆出来,再给出可复制的配置片段。你要做的是把旧代码里对 Assistant/Thread/Run 的依赖,逐个替换成 Prompt/Conversation/Response/Item 的用法。
先看映射关系:
| Assistants 旧对象 | Responses 侧对象 | 职责变化 |
|---|---|---|
| Assistants | Prompts | 模型、工具、instructions 等配置,变成可版本化 Prompt |
| Threads | Conversations | 保存消息、工具调用、输出等 Item |
| Runs | Responses | 接收输入返回输出,工具循环由应用显式管理 |
| Run steps | Items | 执行过程以 Item 流形式呈现 |
这里变的不只是名词。旧 Assistant 是服务端持久配置,你建一次、存个 ID、以后每次 run 都引用它。新模型里,官方建议在控制台把 instruction 和 tool bundle 建成具名 Prompt,代码里引用稳定的 Prompt ID。这样配置和代码解耦,改 prompt 不用发版。如果你暂时不想用控制台 Prompt,也可以把 instructions 直接写在请求里,但长期维护建议走 Prompt ID。
会话部分,旧 Thread 主要存消息,新 Conversation 存的是 Item 流,包括消息、工具调用和输出。历史裁剪、上下文窗口管理这些,过去服务端帮你做一部分,现在更明确地由应用侧负责。这意味着你要自己决定「带多少历史进下一次请求」。
下面是一个可复制的 Responses 请求配置,用 JSON 形式表达,方便你对照自己的请求体:
{ "model": "你的模型ID", "instructions": "你是一个客服助手,只根据知识库回答。", "input": [ { "role": "user", "content": "帮我查一下订单状态" } ], "tools": [ { "type": "function", "name": "query_order", "description": "根据订单号查询订单状态", "parameters": { "type": "object", "properties": { "order_id": { "type": "string" } }, "required": ["order_id"] } } ] }对应到 Python SDK 调用:
response = client.responses.create( model="你的模型ID", instructions="你是一个客服助手,只根据知识库回答。", input=[ {"role": "user", "content": "帮我查一下订单状态"} ], tools=[ { "type": "function", "name": "query_order", "description": "根据订单号查询订单状态", "parameters": { "type": "object", "properties": {"order_id": {"type": "string"}}, "required": ["order_id"], }, } ], )如果你之前用settings.json或 TOML 管理配置,可以这样组织,把 Base URL、Key、Model ID 三件套集中管理:
[taotoken] base_url = "https://taotoken.net/api" api_key_env = "TAOTOKEN_API_KEY" model = "你的模型ID" [agent] prompt_id = "你的PromptID" max_history_items = 20工具调用循环是迁移里最容易出问题的地方。旧 Run 会自动处理「模型要求调用工具 → 你返回结果 → 模型继续」这个循环,新模型下你要自己写。基本结构是:拿到 response 后检查有没有 tool call,有就执行工具、把结果作为新 Item 追加进 input,再发一次请求,直到没有 tool call 为止。这个循环必须显式写,不能指望服务端帮你转。
4. 用一次对话请求验证迁移是否成功
配置改完,别急着上生产。先用一次最小对话请求验证链路通不通,再逐步加多轮和工具调用。验证的目标有三个:请求能到达、返回结构符合预期、会话状态能接续。
第一步,跑一个无工具的纯对话请求:
response = client.responses.create( model="你的模型ID", input=[{"role": "user", "content": "用一句话说明你是什么模型"}], ) print(response.output_text)如果这一步能打印出正常回答,说明 Base URL、Key、模型 ID 三件套没问题。如果报 401,是 Key 问题;如果报 404,多半是 Base URL 拼错或模型 ID 不存在;如果报连接错误,检查网络出口。
第二步,验证多轮会话。旧 Thread 的用法是建一次 Thread,后续消息都往同一个 Thread 里塞。新模型下你要自己维护 Item 列表,把历史带上:
history = [] def chat(user_input): history.append({"role": "user", "content": user_input}) response = client.responses.create( model="你的模型ID", input=history, ) answer = response.output_text history.append({"role": "assistant", "content": answer}) return answer print(chat("我叫小明")) print(chat("我叫什么名字"))第二次请求应该能答出「小明」。如果答不出来,说明历史没带上,或者你只传了最新一条消息。这是迁移后最常见的「会话状态丢失」——不是服务端丢了,是你没把 Item 流维护好。
第三步,验证工具调用。给模型一个会触发工具的输入,观察返回里有没有 tool call 结构:
response = client.responses.create( model="你的模型ID", input=[{"role": "user", "content": "帮我查订单 A123"}], tools=[...], ) for item in response.output: if item.type == "function_call": print("触发工具:", item.name, item.arguments)看到function_call就说明工具声明生效了。接下来你要自己执行工具、把结果追加回 input、再请求一次,直到模型给出最终文本。这一步跑通,才算迁移完成。
验证通过后,建议把这次请求的完整响应结构打印出来存一份,作为回归测试的基线。后面改 prompt 或换模型时,拿它对比,能快速发现行为漂移。
5. 迁移常见报错排查:401、local proxy failed、reading choices、OAuth
迁移过程中报错集中在几类,我按真实遇到的顺序列出来,对照排查。
401 Unauthorized。最常见的原因是 Key 没读到或读错。检查环境变量名是否和代码里一致,检查 Key 有没有多余空格或换行。如果你把 Key 写在.env里,确认加载顺序在客户端初始化之前。还有一种情况是 Key 被吊销或额度用尽,去控制台确认状态。
local proxy failed / connection error。这类报错通常出现在 Base URL 配置错误时。确认你写的是https://taotoken.net/api,没有多余路径、没有尾部斜杠、没有自己加/v1。SDK 会自己拼路径,你加多了就变成无效地址。另外检查本地是否有残留的代理环境变量(HTTP_PROXY、HTTPS_PROXY)指向了不可用的地址,清掉再试。
reading 'choices' of undefined。这个报错说明代码在按旧的 Chat Completions 结构解析响应,但 Responses API 返回的结构不一样。旧代码里习惯写response.choices[0].message.content,新结构里文本在response.output_text,输出项在response.output数组里。把解析逻辑改过来,别继续用choices。
OAuth / authentication 相关报错。如果你用的是某些 CLI 工具或 IDE 插件,它们可能走的是 OAuth 登录流程而不是 API Key。迁移到 TaoToken 时,要在工具的配置里显式填 Base URL、API Key、Model ID 三件套,不要依赖它默认的登录态。以 Claude Code 这类工具为例,配置里要写全:
{ "base_url": "https://taotoken.net/api", "api_key": "你的密钥", "model": "你的模型ID" }三件套缺一个都可能报鉴权或模型找不到。如果你用 Cline、CC Switch 这类支持 MCP 的工具,同样在配置里把这三项填全,别只改其中一项。
工具调用死循环。表现是模型反复要求调用同一个工具,请求停不下来。原因通常是工具返回结果格式不对,模型读不懂,于是再调一次。检查你追加回 input 的工具结果 Item 结构是否符合规范,字段名和类型别写错。另外给循环加一个最大轮次上限,比如 5 次,防止意外。
历史越长越慢或报超长。旧 Thread 由服务端管理上下文,新模型下你要自己裁剪。按 Item 数量或 token 估算截断,保留最近 N 条,或者做摘要压缩。别把全部历史无脑塞进去。
排查时建议开 SDK 的日志,把请求体和响应体打出来看。很多问题看一眼原始结构就清楚了,比猜快得多。
6. 迁移完成后继续用 Responses API 做长期 Agent
迁移跑通只是起点。Responses API 的对象模型更适合做长期演进的 Agent,因为配置、会话、执行三层职责清晰,你可以分别优化。配置层用 Prompt ID 做版本管理,改 prompt 不动代码;会话层自己控制 Item 流,做裁剪和摘要;执行层显式管理工具循环,方便加超时、重试和并发。
如果你打算长期做编码类或 Agent 类应用,建议把调用额度规划一下,Coding Plan 适合持续高频的编码场景:
https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding_plan&utm_campaign=rewrite调试模型行为、对比不同模型输出时,可以直接在模型对话页面里试:
https://taotoken.net/chat?utm_source=taotoken_aicg_blog_end&utm_content=model_chat&utm_campaign=rewrite接入细节和参数说明以文档为准:
https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite最后给一个实用建议:把这次迁移当成一次架构梳理的机会。旧代码里那些「反正服务端会管」的假设,现在都要显式写出来。写清楚之后,你的 Agent 反而更可控——历史怎么裁、工具怎么重试、失败怎么降级,全在你手里。迁移完成后,先跑一周回归测试,覆盖冷启动恢复、多轮状态、工具调用中断与重试、文件检索链路,确认稳定再下线旧代码路径。