为什么我放弃了传统方案,改用 OpenAI Responses API

research进阶8 分钟阅读2026/10/10

为什么我放弃了传统方案,改用 OpenAI Responses API

起因:一个让我崩溃的 Bug

上个月我在维护公司的客服机器人时,遇到了一个经典的诡异问题:对话进行到第 15 轮左右,机器人开始“失忆”,把用户前面说过的订单号忘得一干二净。

排查了一整晚,问题根源很典型:我们的 Chat Completions 方案需要手动维护 messages 数组,而有个同事在某个分支里用切片截断历史时,把 system 消息也一起切掉了。就这么一行代码,让我加班到凌晨两点。

那天我决定认真研究一下 OpenAI 的 Responses API——它宣称能自动管理对话状态。用了一周后,我把项目里的旧方案全部迁移了过去。这篇文章记录我踩过的坑和真实的迁移过程。

传统方案的痛点到底在哪

先说说我们原来的架构。基于 Chat Completions,每次请求都要传完整的对话历史:

response = client.chat.completions.create(
    model="gpt-4o",
    messages=[
        {"role": "system", "content": "你是客服助手"},
        {"role": "user", "content": "我上周下单的订单在哪"},
        {"role": "assistant", "content": "请提供订单号"},
        # ... 手动维护,越滚越长
    ]
)

这套方案有三个让人头疼的地方:

  1. 状态管理全靠手动。数据库里要存一份完整的 messages JSON,每次请求全量上传,第 50 轮对话时 token 消耗和延迟都很难看。
  2. 工具调用结果拼接繁琐。函数调用后要手动构造 role: "tool" 的消息塞回数组,格式稍有差错就报 400。
  3. 多步骤任务(比如网页浏览、文件生成)需要自己编排循环,代码里全是状态机。

迁移实录:三步走

第一步:最简单的替换

Responses API 的基本调用长这样:

from openai import OpenAI
client = OpenAI()

response = client.responses.create(
    model="gpt-4o",
    input="帮我查一下北京今天的天气"
)

print(response.output_text)

第一个惊喜:response.output_text 是个便捷属性,直接拿到最终文本,不用再遍历 choices[0].message.content。虽然是小事,但代码可读性确实好了不少。

第二步:状态管理(这是我要的核心)

之前那个“失忆” Bug 的解法在这里变得极其简单。第一次请求后拿到 response.id,后续请求直接引用:

# 第一轮
response = client.responses.create(
    model="gpt-4o",
    instructions="你是客服助手",  # system prompt 有了专属字段,永远不会被误删
    input="我的订单号是 A12345"
)

# 第二轮,只传新输入 + previous_response_id
response2 = client.responses.create(
    model="gpt-4o",
    previous_response_id=response.id,
    input="刚才那个订单到哪了?"
)

服务端会自动把上下文拼好。我特意测了 30 轮长对话,记忆完好无损。之前那类 Bug 直接从代码里消失了。

注意:服务端默认保留状态 30 天。如果想完全不落盘,创建时加 store: false,但同时也就不能用 previous_response_id 了——这是个取舍。

第三步:工具调用的简化

这是第二个大惊喜。用内置工具时,连自己写执行循环都省了:

response = client.responses.create(
    model="gpt-4o",
    tools=[{"type": "web_search_preview"}],
    input="OpenAI 最近发布了什么新模型?"
)

模型自己搜索、自己整合,一次调用搞定。以前用 Chat Completions 做网页浏览,我得自己写 fetch、截断网页内容、再拼回消息——大概 80 行代码。

自定义函数工具当然还是需要你自己执行,但返回结果的格式简化了:

response = client.responses.create(
    model="gpt-4o",
    input="北京现在几度?",
    tools=[{
        "type": "function",
        "name": "get_weather",
        "description": "查询城市天气",
        "parameters": {"type": "object", "properties": {"city": {"type": "string"}}}
    }]
)
# 执行函数后,把结果送回去
response2 = client.responses.create(
    model="gpt-4o",
    previous_response_id=response.id,
    input=[{
        "type": "function_call_output",
        "call_id": response.output[0].call_id,
        "output": '{"temp": "25°C"}'
    }]
)

迁移过程中踩的坑

诚实地说,迁移不是一帆风顺的:

坑 1:max_tokens 改名了。 新参数叫 max_output_tokens。我直接照搬旧参数,收到报错才反应过来。同样,temperature 等参数基本兼容,但一些细节行为有差异。

坑 2:响应结构完全不同。 response.output 是个列表,里面可能混合 message、function_call 等多种类型的 item。我在解析时一开始假设了固定顺序,结果遇到工具调用和文本混合输出的场景就崩了。正确做法是按 type 字段过滤:

for item in response.output:
    if item.type == "message":
        print(item.content[0].text)

坑 3:状态存储的隐私问题。 我们是医疗相关业务,法务团队对“对话内容存在 OpenAI 服务端 30 天”很有顾虑。最后的方案是敏感会话用 store: false + 自己加密存储,普通会话用 previous_response_id。不是所有场景都适合全盘迁移。

实用建议

  1. 不要一次性全量迁移。我先用一个低风险的内部工具验证了两周,才动核心客服系统。
  2. 给 previous_response_id 链加监控。如果用户清理了历史会话,引用失效的 ID 会报错,要做好降级(重新开一段对话)。
  3. instructions 字段好好利用。把系统提示词从 messages 里挪出来,语义更清晰,也避免了被截断逻辑误伤。
  4. 长对话注意 token 上限。服务端拼上下文不等于无限上下文,超长链路依然要设计摘要或分段机制。

诚实评估

Responses API 不是银弹。它的状态管理把你锁在了 OpenAI 的存储上,多云架构的团队会犹豫;生态和社区资料目前也少于成熟的 Chat Completions,遇到冷门报错时 Google 出来的结果不多。

但对于我这种中小团队、单供应商、被手动状态管理折磨够了的人来说,这次迁移让核心编排代码减少了大约 40%,最难缠的那类 Bug 直接根除。这个收益,值得。

相关 Agent

D

DeepSeek V4

DeepSeek最新开源MoE大模型,671B参数,推理和编程能力顶尖,成本极低

了解更多 →