登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  科技周边 >  人工智能

OpenAI Responses API 怎样用 previous_response_id 延续多轮对话

来源:17golang原创

时间:2026-10-25 00:48:42 481浏览 收藏

我第一次把 Responses API 接进多轮助手时,最容易误判的一点是:只要把上一轮的 response.id 传给下一轮,就等于拥有了一个永久会话。实际上,previous_response_id 只是把响应串成一条上下文链;应用仍然要负责保存这条链的入口、处理失败恢复,并决定什么时候需要更持久的会话对象。

这篇文章只解决一个问题:怎样用 previous_response_id 延续多轮对话,以及它与手动维护历史、Conversations API 的边界。官方资料入口是 https://developers.openai.com/api/docs/guides/conversation-state,可直接复制打开。

先把三种状态管理方式分开

Responses API 的多轮状态大致有三种组织方式。用 previous_response_id 是“沿着上一条响应继续问”;使用 Conversations API 是“给一段长期会话一个独立的 conversation_id”;手动历史则是应用自己把过去的输入和输出整理成下一次请求的 input。

方式适合的压力应用需要保存什么
previous_response_id简单的串行追问、短链路任务最后一次响应 ID 与用户会话的对应关系
Conversations API跨请求、跨设备或长期保存的会话conversation_id,以及业务侧的权限和归属
手动 input 历史需要自己裁剪、脱敏、重排或迁移上下文可重放的消息和输出项,以及裁剪策略

这里的关键判断不是“哪个 API 更高级”,而是上下文的所有权在哪里。链式响应把上下文关联交给 API,手动历史把关联交给应用,而 Conversations API 则把长期会话变成一个可以单独管理的资源。

Responses API、previous_response_id、Conversations API、手动 input 历史和业务会话记录的静态关系图
图1:静态关系说明图,展示三种状态管理入口及其应用持久化边界。

用 response.id 接上下一轮请求

最小链路只有三件事:首轮创建 Response,保存返回对象的 id,下一轮把它放到 previous_response_id。下面的 Node.js 示例故意把状态保存写成函数参数,方便看出业务会话和 API 响应链不是同一个对象。

import OpenAI from "openai";

const client = new OpenAI();

export async function continueConversation(lastResponseId, userText) {
  // 首轮没有 ID 时从新的输入开始;有 ID 时让 API 接续上一轮响应。
  const params = {
    model: process.env.OPENAI_MODEL,
    input: [{ role: "user", content: userText }],
    store: true,
  };

  if (lastResponseId) {
    // 只关联上一轮响应,不把业务用户 ID 直接当作 API 的响应 ID。
    params.previous_response_id = lastResponseId;
  }

  const response = await client.responses.create(params);
  // 调用方应把这个 ID 与自己的会话记录一起保存。
  return { text: response.output_text, lastResponseId: response.id };
}

第一次调用时,返回值中的 lastResponseId 会成为第二次调用的输入。第二次调用完成后,要用新的 response.id 覆盖旧值;不要一直保存首轮 ID,否则每次都从相同节点分叉。

这个示例没有把 API key、用户标识或真实响应 ID写进文章,也没有把调用结果当成已经在本机运行过的证据。它只是展示请求字段之间的静态关系。

指令不会因为链式关联自动继承

这是最值得单独提醒的边界:previous_response_id 负责传递上下文关联,并不等于应用层配置会永远沿用。官方文档特别说明,使用 previous_response_id 时,上一轮的 instructions 不会自动带到下一轮。

因此,下面这种“第一轮设定了回答格式,第二轮什么都不补”的写法不适合作为稳定的生产约定:

const next = await client.responses.create({
  model: process.env.OPENAI_MODEL,
  previous_response_id: previous.id,
  // 中文说明:如果本轮仍需遵守输出格式,应在这里再次明确要求。
  input: "把刚才的结论改成三条检查清单。",
});

更稳妥的做法是把每一轮都需要的稳定规则放进本轮请求的 instructions,把只属于当前问题的内容放进 input。这样即使以后更换状态载体,也不必依赖“隐藏的上一轮配置”。

store、响应保留与输入成本要一起看

链式调用还有三个经常被混为一谈的概念。

  • store 是响应对象是否由 API 保存的请求选项,不是业务数据库里的会话表。
  • 保存响应不等于无限期保存;响应对象有默认保留周期,长期会话应评估 Conversations API 或应用自己的持久化策略。
  • 即使使用 previous_response_id,链中先前的输入 token 仍会作为输入计费,不能把“少传几段文本”理解为“完全没有上下文成本”。

在服务端,至少应记录用户会话 ID、最后一次响应 ID、状态更新时间和失败状态。用户会话 ID用于找回业务状态,响应 ID用于续接 API 链,两者不要混用。

首轮 response、response.id、previous_response_id、下一轮 input、instructions、store 与链式输入 tokens 的静态关系图
图2:静态调用关系说明图,展示响应关联、请求配置和链式输入成本的边界。

业务服务怎样保存最后一个响应 ID

可以把续接逻辑包在一个小的状态仓库后面。下面用 Python 写一个最小接口,重点不在数据库选型,而在“成功后才推进指针”:请求失败时不能先把新 ID 写入会话,否则下一次恢复会从一个不存在或未完成的节点继续。

from openai import OpenAI

client = OpenAI()

def ask(session, text):
    # session 只保存业务会话和最后一次成功响应 ID,不保存密钥。
    params = {
        "model": "${OPENAI_MODEL}",
        "input": [{"role": "user", "content": text}],
        "store": True,
    }
    if session.get("last_response_id"):
        # 中文说明:只从最后一次成功响应继续,避免重复使用旧分支。
        params["previous_response_id"] = session["last_response_id"]

    response = client.responses.create(**params)
    # 中文说明:API 成功返回后再推进本地游标,失败时保留旧游标以便重试。
    session["last_response_id"] = response.id
    return response.output_text

生产代码还需要给 session 加上并发控制。例如同一个用户同时提交两条消息时,两个请求都读取到相同的旧 ID,就可能形成两个分支。可以采用数据库乐观锁、队列串行化,或明确允许分支并把每个分支单独命名。

什么时候应该改用 Conversations API

如果只是一个页面上的连续追问,previous_response_id 足够直观;如果要让会话跨设备、跨作业或长期存在,Conversations API 更容易表达真实的资源关系。官方文档描述的 conversation 可以保存消息、工具调用和工具输出等 item,后续 Responses 请求通过 conversation 继续使用它。

还有一种情况适合手动历史:你必须在发给模型前做敏感字段脱敏、按租户重排、按预算裁剪,或者要把历史迁移到另一套模型服务。此时应用掌握完整的可重放输入,会比依赖单条响应链更容易审计。

可以用下面的清单做选择:

  1. 只需把上一轮答案接到下一轮:使用 previous_response_id。
  2. 需要长期、跨设备管理一个会话资源:优先考虑 Conversations API。
  3. 需要脱敏、裁剪、重排或跨供应商迁移:自己保存并组装 input 历史。
  4. 无论选哪种方式,都单独保存业务会话身份、权限和失败恢复状态。

相关问题

previous_response_id 能代替业务数据库吗?

不能。它是 API 请求的上下文关联字段,不包含你的用户权限、租户归属、审计信息和业务状态。

下一轮是否必须再次传完整历史?

使用 previous_response_id 时不需要按手动历史方式重放全部输入,但链式上下文仍然会产生输入 token 成本;如果需要自定义裁剪,就改为手动管理历史。

为什么要保存最新 response.id 而不是首轮 ID?

因为每个响应都是链上的新节点。保存最新 ID 才能让下一轮从最近一次成功状态继续,而不是反复从旧节点分叉。

把 previous_response_id 看成“响应链指针”,而不是“万能会话开关”,多轮对话的设计就会清楚很多:短链路用它,长期资源用 Conversations API,需要控制上下文就自己维护历史。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>