在调用 LLM 构建聊天应用时,模型看似具备连续对话的能力。但在底层协议中,模型本身是完全无状态(Stateless)的。搞清楚“上下文如何传递”,是理解大模型系统架构的关键。
核心差异:状态由谁来管?
OpenAI 的两大主流接口展现了不同的设计思路:
| 维度 | Chat Completions API | Responses API |
|---|---|---|
| 状态维护方 | 客户端应用层 | API 服务端 |
| 接续方式 | 每次完整回传历史 messages 数组 |
仅需传入上一轮的 previous_response_id |
| 若不传上下文 | 独立隔离(互为“平行世界”) | 独立隔离(互为“平行世界”) |
| 多厂商兼容性 | 极高(已成为跨模型通用工业事实标准) | 偏低(OpenAI 专属生态规范) |
两者的实现方式对比
1. Chat Completions API:客户端拼接 如果不把过往的对话重新打包发送,第 2 次请求就是一个全新的平行世界,模型对上一轮一无所知。
# 第 3 轮提问时,应用端必须手动把前两轮完整拼装进去
messages = [
{"role": "user", "content": "我叫张三,请记住我的名字。"},
{"role": "assistant", "content": "明白,您叫张三。"},
{"role": "user", "content": "我叫什么名字?"}
]
response = client.chat.completions.create(model="gpt-4o", messages=messages)
逻辑本质:LLM 没有记住任何事,只是应用层每次都把历史记录当成新的 Prompt 完整投喂了一遍。
2. Responses API:服务端引用 Responses API 引入了链式引用机制,客户端无需组装长数组,只需携带上一次响应的唯一凭证:
# 第 1 轮
resp1 = client.responses.create(model="gpt-4o", input="我叫张三")
# 第 2 轮:直接关联上一轮的 Response ID
resp2 = client.responses.create(
model="gpt-4o",
previous_response_id=resp1.id,
input="我叫什么名字?"
)
逻辑本质:状态搬运的工作从客户端转移到了 OpenAI 服务端。在推理前,服务端根据 ID 调出上下文并自动填入 Prompt。
使用 Responses API 的技术约束
- 指令不会默认继承:首轮设置的
instructions(系统指令)不会随previous_response_id自动顺延,后续调用仍需单独管理。 - 并非持久数据库:服务端缓存的 Response 存在生存周期与数据保留策略限制(如 Zero Data Retention 模式),不可当作长期用户画像库使用。
架构启示:从 Prompt 拼装到企业级状态机
模型从来没有诞生过人类般的持久记忆,所谓的“对话连续性(Conversation Continuity)”,本质始终是“如何将过去的上下文精准输入给当前的推理引擎”。
在真实的生产级架构中,上下文早已超出了“聊天气泡记录”的范畴。一个成熟的 AI 平台(如企业级 Agent 编排框架),需要管理:
- 当前业务节点的推进状态(Workflow State)
- 跨会话的持久化用户画像(Long-term Context)
- 动态外部知识库检索结果(RAG Context)
对话连续性的工程核心,从不是纠结于单次 API 的拼接手法,而是在系统层构建一套能够兼顾跨平台兼容、业务状态持久化与 token 成本控制的会话状态管理架构。