# OpenAI Assistants API 迁移到 Responses API：停用后的生产切换

> Assistants API 已于 2026 年 8 月 26 日停用。现在要从应用保存的配置和记录迁出：恢复可用历史，重新管理状态与工具调用，再把流量切到经过验证的 Responses 实现。故障退路也必须在新系统中成立。

- URL: https://blog.laozhang.ai/zh/posts/openai-assistants-api-to-responses-api
- Published: 2026-07-28
- Updated: 2026-10-06
- Author: LaoZhang AI Team (https://blog.laozhang.ai/zh/about)
- Topic: API 接入指南
- Tags: OpenAI Assistants API, Responses API, Conversations API, API 迁移, Function Calling

---
OpenAI Assistants API 已于 **2026 年 8 月 26 日停用**，现在不能再靠旧接口导出 Thread、运行 Assistant，或把流量切回旧 Run。[官方迁移指南](https://developers.openai.com/api/docs/assistants/migration)明确要求使用应用已经保存的消息。可执行的迁移起点是：取回自己的指令、工具定义和历史记录，先恢复一条完整的 Responses 工作流，再验证检索、事件和生产切换。

如果手上只有 `asst_`、`thread_` 或 `run_` ID，没有配置、消息或备份，它们不足以恢复原对话。此时仍可根据业务规则建立新会话；旧历史只能标为不可用，不能让模型补写成“恢复记录”。

本文针对官方 OpenAI 的 Assistants → Responses 迁移。Azure、第三方兼容接口的对象、工具和保存规则须分别核对。示例依据当前官方文档编写，做了纯离线语法和合成数据检查，**没有实际调用 OpenAI、运行 SDK 或证明生产行为等价**。

## 先恢复配置，让新请求不再依赖 Assistant ID

把原 Assistant 的 `instructions`、模型选择、函数 schema、检索范围和输出格式收回应用配置，给它一个可固定、可部署的版本。来源可以是停用前的导出、应用数据库、配置仓库或已有备份；缺失的规则由业务负责人重新确认，不从 Assistant ID 推算。

官方迁移页还保留 `Assistant → Prompt` 的映射，但 reusable Prompt objects 与 `v1/prompts` 也计划于 **2026 年 11 月 30 日停用**。长期实现应直接发送指令与工具定义，避免迁一次又依赖下一项即将退役的服务。[Prompt 对象迁出指南](https://developers.openai.com/api/docs/guides/prompting/migrate-from-prompt-object)给出的方向就是把内容移入应用代码。

| 原来的依赖 | 现在放在哪里 | 实际恢复动作 |
|---|---|---|
| Assistant 指令、模型与工具 | 版本化应用配置 | 从保存的定义重建，审查缺失字段，固定配置版本 |
| Thread 与用户的关系 | 应用会话表 | 保留租户、用户、业务会话与旧 ID 的关系，另建新状态 |
| Message 历史 | 本地归档，再转换为受支持的输入 | 恢复顺序和角色；附件、引用及工具记录单独处理 |
| Run 与 `requires_action` | Response 与应用工具循环 | 识别 typed Items，执行函数，回传对应 `call_id` |
| Assistant/Thread 绑定的文件 | 明确的检索配置 | 核对 vector store 和文件的现存状态，再指定搜索范围 |

例如订单客服配置可以保留“必须查到记录再回答物流状态”的规则。下面完整函数示例把配置写在代码里；生产中可将同样的定义存入受版本控制的 JSON 或配置系统。不要把用户输入直接写进系统指令，也不要让模型决定当前登录用户或租户身份。

## 把已保存的 Thread 记录转成可继续的历史

![将应用已保存的历史按纯文本、附件引用与工具记录分类，保留原件后恢复 Responses 对话](https://blog.laozhang.ai/posts/zh/openai-assistants-api-to-responses-api/img/archive-recovery.webp)

先保留原始导出，再产生新输入。**转换成功不等于所有历史都能继续执行**：旧消息里可能只有显示文字，没有原附件、检索证据或完整工具结果。已经完成的业务动作应从业务数据库确认；未完成的旧 Run 不能继续轮询，需在新系统中重新安排任务并核对是否已经产生副作用。

下面是最小的已保存记录格式，保存为 `archive.json`。`messages` 必须按时间升序，内容是原始存档，不是重新向旧接口取回的结果。若备份另有 Run steps 或工具日志，先将它们归入 `tool_events`；没有完整记录时先按只读历史处理，不能因为这个数组为空就推断工具没有执行过。

```json
{
  "thread_id": "thread_archived_example",
  "messages": [
    {
      "id": "msg_saved_1",
      "role": "user",
      "created_at": 1780000000,
      "attachments": [],
      "content": [
        {"type": "text", "text": {"value": "订单是 DEMO-7", "annotations": []}}
      ]
    },
    {
      "id": "msg_saved_2",
      "role": "assistant",
      "created_at": 1780000001,
      "attachments": [],
      "content": [
        {"type": "text", "text": {"value": "请问要查询配送状态吗？", "annotations": []}}
      ]
    }
  ],
  "tool_events": []
}
```

这个转换器只接受纯文本历史。它把完整原件保留在转换文件的 `original` 中，把可继续的部分放到 `input`；遇到附件、引用、非文本内容或工具事件就阻止自动续聊，并指出需要处理的消息。保存为 `convert_archive.py`：

```python
import json
import sys
from pathlib import Path


def convert(archive):
    items, blockers = [], []
    messages = archive.get("messages")
    if not isinstance(messages, list) or not messages:
        raise ValueError("需要完整消息记录，只有 Thread ID 不够")
    previous_time = None
    for index, message in enumerate(messages):
        label = message.get("id", f"message-{index}")
        timestamp = message.get("created_at")
        if type(timestamp) is not int:
            raise ValueError(f"{label}: 缺少有效时间")
        if previous_time is not None and timestamp < previous_time:
            raise ValueError("消息不是时间升序，请先核对原始顺序")
        previous_time = timestamp
        role = message.get("role")
        if role not in ("user", "assistant"):
            blockers.append(f"{label}: 不支持的角色")
            continue
        parts = message.get("content")
        if not isinstance(parts, list) or not parts:
            blockers.append(f"{label}: 缺少内容")
            continue
        content = []
        if message.get("attachments"):
            blockers.append(f"{label}: 附件需要重新确认访问与内容")
        for part in parts:
            if part.get("type") != "text":
                blockers.append(f"{label}: 非文本内容需要单独转换")
                continue
            text = part.get("text", {})
            if not isinstance(text.get("value"), str):
                blockers.append(f"{label}: 文本内容无效")
                continue
            if text.get("annotations"):
                blockers.append(f"{label}: 引用需要核对原文件")
            content.append({
                "type": "input_text" if role == "user" else "output_text",
                "text": text["value"],
            })
        if content:
            items.append({"role": role, "content": content})
    if archive.get("tool_events"):
        blockers.append("工具事件需确认结果、授权和业务动作是否已完成")
    return {
        "legacy_thread_id": archive.get("thread_id"),
        "ready_to_resume": not blockers,
        "input": items,
        "blockers": blockers,
        "original": archive,
    }


if __name__ == "__main__":
    source, destination = map(Path, sys.argv[1:3])
    if destination.exists():
        raise FileExistsError("不覆盖已有转换结果")
    result = convert(json.loads(source.read_text(encoding="utf-8")))
    destination.write_text(
        json.dumps(result, ensure_ascii=False, indent=2), encoding="utf-8"
    )
    print(json.dumps({
        "ready_to_resume": result["ready_to_resume"],
        "blockers": result["blockers"],
    }, ensure_ascii=False))
```

运行 `python convert_archive.py archive.json converted.json`，先看 `ready_to_resume`。为 `false` 时，转换结果仅供整理与展示，不应直接交给下面的续聊程序。附件可以在授权范围内重新接入；旧引用应保留原始位置，并重新核对文件是否可读。只有文字但缺少证据的历史，可作为只读记录展示；需要继续处理的业务事实应从当前业务系统重新获取。

官方历史示例同样区分用户的 `input_text` 与助手的 `output_text`，但其中调用旧 Thread messages API 的部分已经失效。[当前迁移说明](https://developers.openai.com/api/docs/assistants/migration)不能被理解为现在还能在线补导出历史。

## Thread 迁到哪里：选一种状态策略，避免重复回放

恢复文本后，再决定后续状态由谁保存。[会话状态文档](https://developers.openai.com/api/docs/guides/conversation-state)提供手动回放、`previous_response_id` 和 Conversations 三种方式。

| 方式 | 每轮实际发送什么 | 适用场景与必要条件 |
|---|---|---|
| 应用自管 Items | 已转换历史、新输入，以及完整的新 `response.output` | 要自己保存、裁剪和删除历史；`store:false` 仅控制 Response 对象保存 |
| `previous_response_id` | 可用的上一条 Response ID，加本轮新增输入 | 短期续接；每轮重发所需指令，不能把 ID 当永久归档 |
| Conversation | 同一个新 Conversation ID，加本轮新增输入 | 跨设备或任务持久会话；应用仍负责用户权限、映射和删除 |

Conversation 会把已有 Items 加到上下文，并在请求完成后保存本轮输入与输出。因此，一旦用转换历史初始化新 Conversation，后续只发送增量，不要再重复整段历史。`conversation` 和 `previous_response_id` 不能同时使用；通过 `previous_response_id` 续接时，上轮 `instructions` 不会自动继承。[Responses 创建参数](https://developers.openai.com/api/reference/resources/responses/methods/create)规定了这两项边界。

选择 Conversation 时，可以在读者自己的授权环境执行 `client.conversations.create(items=converted["input"])`，再把返回的新 ID 与业务会话关联。应用端映射至少包含租户、用户、业务会话、旧 Thread ID、新状态标识与配置版本；授权检查要先于任何会话读取。把 ID 的前缀替换成 `conv_` 不会创建对象，也不会迁移数据。

手动回放尤其要保留**新 Response 的全部输出 Items**，包括函数调用、工具结果、不透明 reasoning 和助手 `phase`，不能把 `output_text` 当完整历史。Python 官方示例追加完整 `response.output`；JavaScript 当前示例用 `toResponseInputItems` 转成可回放输入。[推理上下文说明](https://developers.openai.com/api/docs/guides/reasoning#keeping-reasoning-items-in-context)也要求工具往返时保留相关 Items。旧归档没有这些内容时，不要生成一份“推理历史”补上。

长对话还要设上下文预算、摘要或裁剪规则，并验证订单编号、已确认事实和未完成任务仍然保留。历史通过 ID 续接也不是免费上下文：官方说明链中的先前输入 token 仍按输入计费。

## 用完整工具循环恢复订单查询，而不是只替换 endpoint

![Responses 返回函数调用后，应用验证权限并执行，再用相同 call_id 回传结果，继续循环取得最终答案](https://blog.laozhang.ai/posts/zh/openai-assistants-api-to-responses-api/img/function-loop.webp)

自定义函数由应用执行。模型输出 `function_call` 后，应用校验参数与用户权限，运行函数，再发送 `function_call_output`。两者用原始 **`call_id`** 关联，不能改用 Item 的 `fc_` ID。[函数调用指南](https://developers.openai.com/api/docs/guides/function-calling)明确允许一次返回零个、一个或多个函数调用；得到工具输出后，模型还可能继续调用。

下面的 `resume_orders.py` 可接上 `converted.json`。前提是读者环境中已有兼容的 OpenAI Python SDK、官方项目凭据，以及获准使用的 Responses 模型 ID；这些前提不由本地语法检查证明。代码中的订单是只读合成数据，不访问真实订单系统。

程序采用应用自管历史。每次对话成功后将完整 Items 保存到 `session.json`，新一轮前保留 `last-ready.json` 检查点；异常时保留未完成状态并停止自动续接，避免把半段工具往返当成正常历史。恢复前先核对本轮工具动作，再决定是否采用检查点。示例限单进程、单会话运行；部署为多用户服务时，须改成受认证保护的会话存储、会话锁和原子提交。

```python
import json
import sys
from pathlib import Path
from openai import OpenAI

MODEL, QUESTION = sys.argv[1:3]
STATE_PATH = Path("session.json")
INSTRUCTIONS = (
    "你是订单客服。订单状态必须来自 get_order_status，不能猜测。"
    "只查询当前用户已获授权的订单；工具返回错误时直接说明无法查询。"
)
TOOLS = [{
    "type": "function",
    "name": "get_order_status",
    "description": "查询当前用户有权限访问的订单配送状态。",
    "strict": True,
    "parameters": {
        "type": "object",
        "properties": {"order_id": {"type": "string"}},
        "required": ["order_id"],
        "additionalProperties": False,
    },
}]

# 合成的当前用户权限；生产中由已认证身份及业务数据库提供。
ALLOWED_ORDERS = {"DEMO-7"}
ORDERS = {"DEMO-7": {"status": "shipped", "carrier": "演示快递"}}


def save_state(history, ready):
    temporary = STATE_PATH.with_suffix(".tmp")
    temporary.write_text(json.dumps({
        "ready": ready, "config_version": "orders-v1", "history": history,
    }, ensure_ascii=False, indent=2), encoding="utf-8")
    temporary.replace(STATE_PATH)


def execute(call):
    if call.name != "get_order_status":
        raise ValueError("未知工具，停止执行")
    args = json.loads(call.arguments)
    if (not isinstance(args, dict) or set(args) != {"order_id"}
            or not isinstance(args["order_id"], str)):
        raise ValueError("工具参数无效")
    order_id = args["order_id"]
    if order_id not in ALLOWED_ORDERS:
        return {"error": "not_authorized"}
    return ORDERS.get(order_id, {"error": "not_found"})


if STATE_PATH.exists():
    state = json.loads(STATE_PATH.read_text(encoding="utf-8"))
    if not state["ready"] or state["config_version"] != "orders-v1":
        raise RuntimeError("会话未完成或配置不匹配，请先人工核对")
    history = state["history"]
else:
    converted = json.loads(Path("converted.json").read_text(encoding="utf-8"))
    if not converted["ready_to_resume"]:
        raise RuntimeError("历史存在未处理内容，不能自动续聊")
    history = converted["input"]

save_state(history, True)
Path("last-ready.json").write_text(
    STATE_PATH.read_text(encoding="utf-8"), encoding="utf-8"
)
history.append({"role": "user", "content": QUESTION})
save_state(history, False)
client = OpenAI()
seen_calls = set()

for _ in range(6):
    response = client.responses.create(
        model=MODEL,
        instructions=INSTRUCTIONS,
        tools=TOOLS,
        input=history,
        store=False,
        parallel_tool_calls=False,
        include=["reasoning.encrypted_content"],
    )
    if response.status != "completed":
        raise RuntimeError(f"Response 未成功完成：{response.status}")
    history.extend(item.model_dump(exclude_none=True) for item in response.output)
    save_state(history, False)
    calls = [item for item in response.output if item.type == "function_call"]
    if calls:
        for call in calls:
            if not call.call_id or call.call_id in seen_calls:
                raise RuntimeError("调用标识缺失或重复")
            seen_calls.add(call.call_id)
            result = execute(call)
            history.append({
                "type": "function_call_output",
                "call_id": call.call_id,
                "output": json.dumps(result, ensure_ascii=False),
            })
        save_state(history, False)
        continue
    if any(item.type != "message" and item.type != "reasoning"
           for item in response.output):
        raise RuntimeError("出现本示例未处理的输出类型")
    texts = [part.text for item in response.output
             if item.type == "message"
             and getattr(item, "phase", None) in (None, "final_answer")
             for part in item.content if part.type == "output_text"]
    if not texts or not "".join(texts).strip():
        raise RuntimeError("没有可显示的最终文本，请检查拒绝或中间输出")
    save_state(history, True)
    print("".join(texts))
    break
else:
    raise RuntimeError("超过工具循环上限，保留现场并停止")
```

在读者的测试环境运行 `python resume_orders.py YOUR_APPROVED_MODEL "这个订单发货了吗？"`，其中模型占位符要换成项目实际支持的 ID。成功判断包括：模型调用获准的订单查询，应用回传同一个 `call_id`，最后答案与合成记录中的 `shipped` 一致，并且 `session.json` 的 `ready` 为 `true`。只看到 HTTP 200、调用请求或本地函数执行，都不足以判定业务完成。后续输入可以沿用同一个本地状态文件；更换用户时必须使用独立且经过授权的会话。

`strict:true` 也不是权限检查。每个 schema 对象必须设 `additionalProperties:false`，所有属性列入 `required`；可选值用可空类型表达。不写 `strict` 时，当前 Responses 会尝试规范化，不兼容则退回 best-effort，并显示 `strict:false`。因此迁移时应明确写出 strict 并保留应用验证。`parallel_tool_calls:false` 只限制为零个或一个调用，并不强制模型一定查订单。[strict 与并行调用规则](https://developers.openai.com/api/docs/guides/function-calling#strict-mode)解释了这些区别。

若工具将来涉及退款、发邮件或修改订单，不能直接换掉这个只读函数就上线。应先由应用验证权限与确认要求，再用持久的业务动作键实现幂等，并记录动作结果。`call_id` 只解决模型调用和输出的配对，不能阻止另一次请求产生相同业务动作。超时后先查动作是否已经提交，再决定重试；取消模型也不会撤回已经发生的退款。

## File Search：找回正确文件，并验证答案的依据

Responses 的 `file_search` 由 OpenAI 执行，不能套用自定义函数的结果回传循环。先核对原 vector store 是否仍存在、属于正确项目、允许当前用户访问；文件在 store 内的处理状态须为 `completed`。旧 Assistant 或 Thread 的绑定关系不会自动决定新请求的搜索范围。[File Search 指南](https://developers.openai.com/api/docs/guides/tools-file-search)说明了文件就绪与显式指定 store 的要求。

下面片段接在已初始化的 Python `client` 后，`MODEL` 使用获准模型，`VECTOR_STORE_ID` 换成已经核对过的真实 store ID。它不创建 store 或上传文件：

```python
VECTOR_STORE_ID = "YOUR_VERIFIED_VECTOR_STORE_ID"
response = client.responses.create(
    model=MODEL,
    store=False,
    input="退款审批额度在什么文件的哪一节？请依据检索内容回答。",
    tools=[{
        "type": "file_search",
        "vector_store_ids": [VECTOR_STORE_ID],
    }],
    include=["file_search_call.results"],
)
if response.status != "completed":
    raise RuntimeError(f"检索请求未成功：{response.status}")
searches = [item for item in response.output if item.type == "file_search_call"]
if not searches or not any(getattr(item, "results", None) for item in searches):
    raise RuntimeError("没有可核对的检索结果，不能判为迁移成功")
print(response.output_text)
print(response.model_dump_json(indent=2))
```

检索结果默认不返回，`include=["file_search_call.results"]` 是为了核对实际证据。对测试问题，应先知道正确文件和段落，再比对结果、最终答案与 message 的 file citations。还要用无结果、过期文件、相互冲突的文件和无权访问的文件检验行为；资料不足时应回答无法确认，而不是编一个审批额度。

多租户场景先由应用选择允许的 store，再在需要时使用文件 metadata filters 缩小范围。过滤条件必须由可信身份与权限生成，不能让用户或模型随意指定另一租户。原始文件确实还在且有权重新使用时，可以在自己的环境重新建立索引；只有旧 file ID 的情况下，不能保证还能取回文件内容。

检索回退可以是已经验证的独立搜索实现、只读资料页面或人工查证。它必须实际能访问同一套获准资料；“继续用旧 Assistants File Search”已经不是退路。

## 改写事件与长任务，让结束、失败和取消各有处理

旧 Run 轮询应按新用途拆开：普通请求等 Response；需要逐字显示时消费 typed SSE；真正长任务再用 `background:true`。不要把每一次旧 Run 都机械变成后台任务。

| 输入或事件 | 新实现的处理方式 |
|---|---|
| `response.created` | 建立请求关联，记录 Response ID |
| `response.output_text.delta` | 追加显示文字，尚不宣告任务完成 |
| 函数 arguments delta/done | 按 Item 聚合参数；完整调用到齐并验证后才执行 |
| `response.completed` | 检查终态、输出类型和业务结果，再提交会话状态 |
| error、失败或不完整输出 | 保留错误与未完成状态，向用户提供可重试或人工处理入口 |

结构化输出要把旧 `response_format` 改为 `text.format`，如 `{"type":"json_schema","name":"order_result","schema":...,"strict":true}`；其中 `schema` 仍要提供完整定义。前端不能把函数参数、reasoning 或工具 Item 拼进普通回复文本。官方的[Responses 迁移指南](https://developers.openai.com/api/docs/guides/migrate-to-responses)分别给出了输出格式与 typed events 的变更。

后台模式下，保存 Response ID，在 `queued` 或 `in_progress` 时调用 `responses.retrieve` 轮询，退出这两个状态只表示进入终态，**不一定成功**。取消在运行的后台任务用 `responses.cancel`；同步任务取消通过结束连接。后台断线后想恢复流，创建时就要同时设 `stream:true`，保存 `sequence_number`，恢复时传 `starting_after`。[后台模式文档](https://developers.openai.com/api/docs/guides/background)还指出当前后台首 token 延迟更高，不能仅因异步就承诺更快。

应用端同时保存任务 ID、状态、最后事件位置和已经完成的工具动作。重连后先恢复状态，不能把“前端没收到完成事件”当成“后台没有执行过”。

## 数据保存：store:false 不能代替完整的保留策略

`store:false` 关闭的是普通 Response 对象保存，不代表所有内容都零留存。当前[数据控制文档](https://developers.openai.com/api/docs/guides/your-data)区分应用状态与滥用监控日志：普通 Response 默认保存 30 天，`store:true` 的相关数据至少保存 30 天，存在文档列出的例外；默认滥用监控日志可能包含内容，通常最多保存 30 天，也有延长例外。

Conversation 与其 Items 没有普通 Response 的 30 天到期机制，保存到删除，而且不符合 ZDR 资格。向 Conversation 附加请求时写 `store:false`，也不能据此声称没有提供商会话历史。文件、vector store、托管容器与第三方服务还有各自的保存规则。

后台模式的当前规则需要单独核对：官方说明 ZDR 项目的后台请求使用 `store:false`，但仍会为异步执行和轮询把数据**临时保存到磁盘约 10 分钟**。MAM 项目的后台请求只有显式 `store:true` 才在轮询期后继续按标准期限保存；省略或设为 `false` 时约 10 分钟后删除。不要沿用“后台必须 store:true”或“ZDR 一律不能用后台”的旧结论；客户合同若连临时保存也不允许，就应选择合适的前台流程。[后台说明](https://developers.openai.com/api/docs/guides/background)与[Responses 数据条款](https://developers.openai.com/api/docs/guides/your-data#v1responses)提供当前适用范围。

上面的手动回放示例仍会在应用机器保存原归档和 `session.json`。这些文件也需要访问控制、删除期限和备份规则；把历史从提供商移回应用，不会自动完成数据最小化。

## 用保存的业务结果验收，再逐步切到新实现

现在的对照基线应是已保存的真实任务输入、可信业务结果和配置，或已经正常运行的 Responses 版本。旧 Assistants 已不能作为在线双跑基线。没有旧输出时，仍可按业务规则建立验收答案，例如授权用户查询正确订单、无权限用户拿不到订单、检索引用能回到指定文件。

先在隔离的测试环境跑完整流程，保留配置版本、使用的 SDK 版本、新输出 Items、工具调用与结果，以及失败分支。工具测试还应覆盖未知工具、无效 JSON、越权参数、零调用、多调用、无最终答案和轮数上限。比较任务是否完成，再看总延迟、token、工具用量与存储开销；换 endpoint 本身不能证明更便宜或质量更高。

切流可以从内部用户和低风险会话开始，按自己业务设定批次。每个会话固定状态策略与配置版本，避免请求一会儿读本地历史、一会儿读另一套 Conversation。需要影子比较时，只让其中一侧提交业务动作，另一侧使用只读数据或合成工具结果；不能让两套实现各退款一次。

| 切换时出现的问题 | 可执行的停止与恢复动作 |
|---|---|
| 新配置回答错误 | 停止增加流量，恢复已验收的 Responses 配置版本 |
| 会话遗漏或串话 | 暂停相关会话，核对用户映射和状态；从最后完整检查点恢复 |
| 工具结果不明 | 查询业务动作记录，确认是否提交，再决定重试或人工处理 |
| 检索无证据或越权 | 停用该搜索路径，转获准的只读资料或人工查证 |
| 新运行环境不可用 | 切到已验证的新运行环境；否则排队、暂停自动处理或人工接管 |

所谓“回滚”，现在是恢复**仍可运行且已验收的新版本**，不是恢复旧 Assistant。若切换前后用了不同状态存储，需先有明确的历史转换和恢复流程；不能仅切 feature flag 就假定对话随之恢复。

最终完成的标志是：新请求只走可用接口，保存的配置与历史能进入新流程，权限和工具结果正确，文件答案可追溯，前端知道成功与失败，异常时有实际可执行的恢复方式。第三方服务接入若出现 URL 或接口兼容问题，可另看[OpenAI base_url 的优先级与实际请求路径](https://blog.laozhang.ai/zh/posts/openai-base-url-override)，但改地址不会自动迁移旧对象或工具合同。

## 常见问题

### 只有 Thread ID，还能恢复旧聊天吗？

仅凭 ID 不能恢复。当前[官方迁移指南](https://developers.openai.com/api/docs/assistants/migration)明确指出旧消息列表接口已失效，须使用应用保存的消息。可以查自己数据库、备份或历史导出；找不到内容时就新建会话，并如实保留历史不可用的状态。

### 必须先创建 Prompt object 才能迁到 Responses 吗？

不必。直接通过应用配置发送 `model`、`instructions`、`tools` 和输出定义即可。Prompt objects 与 `v1/prompts` 计划于 2026 年 11 月 30 日停用，[官方迁出方案](https://developers.openai.com/api/docs/guides/prompting/migrate-from-prompt-object)推荐将内容移入应用代码。

### 函数调用成功，为什么用户仍没有答案？

因为收到 `function_call` 只是模型请求执行。应用还要执行函数，用同一个 `call_id` 回传 `function_call_output`，继续请求，直到拿到可用的最终输出。结果可能还需要更多调用；失败、不完整或只有中间消息时，应按未完成任务处理。[函数调用流程](https://developers.openai.com/api/docs/guides/function-calling)定义了完整往返。

### 迁移失败后，能重新打开 Assistants 作为回退吗？

不能把已停用的 Assistants 当可用回退。应恢复经过验收的 Responses 版本、切换已验证的新运行环境，或暂停自动处理并由人工接管。未确认的工具动作先查业务结果，避免重复执行。

## 参考来源

本文引用的外部页面，按正文出现顺序排列。最后更新于 2026-10-06。

- [官方迁移指南](https://developers.openai.com/api/docs/assistants/migration) (developers.openai.com)
- [Prompt 对象迁出指南](https://developers.openai.com/api/docs/guides/prompting/migrate-from-prompt-object) (developers.openai.com)
- [会话状态文档](https://developers.openai.com/api/docs/guides/conversation-state) (developers.openai.com)
- [Responses 创建参数](https://developers.openai.com/api/reference/resources/responses/methods/create) (developers.openai.com)
- [推理上下文说明](https://developers.openai.com/api/docs/guides/reasoning) (developers.openai.com)
- [函数调用指南](https://developers.openai.com/api/docs/guides/function-calling) (developers.openai.com)
- [File Search 指南](https://developers.openai.com/api/docs/guides/tools-file-search) (developers.openai.com)
- [Responses 迁移指南](https://developers.openai.com/api/docs/guides/migrate-to-responses) (developers.openai.com)
- [后台模式文档](https://developers.openai.com/api/docs/guides/background) (developers.openai.com)
- [数据控制文档](https://developers.openai.com/api/docs/guides/your-data) (developers.openai.com)
