通过程序调用 ChatGPT 时,如何在不重复提交历史消息的情况下继续对话?
能否省去客户端每轮重传全部历史,取决于使用的 API:
- 题目中的
client.chat.completions.create()需要你管理历史消息,每轮提交回答当前问题所需的上下文。 - 使用 Responses API 时,可以通过
previous_response_id续接上一轮,也可以通过 Conversations API 保存会话,后续只发送会话标识和新消息。官方会话状态文档
省去历史消息的重复传输后,服务端仍需将相关历史作为上下文,参与计算和计费。能否减少重复计算,取决于缓存是否命中。
为什么复用同一个 client 不能自动续聊?
OpenAI() 创建的是 API 客户端。调用 chat.completions.create() 时,复用同一个客户端、API Key 或 HTTP 连接,都不会自动关联前后两次对话。
如果第二次请求只包含:
messages=[
{"role": "user", "content": "Can you elaborate more on that?"}
]
模型没有收到上一轮内容,就无法可靠判断 that 指什么。使用 Chat Completions 进行多轮对话,需要通过 messages 提供上下文。官方会话状态说明
题目中的环境变量注释也需要更正:Python SDK 默认读取的是 OPENAI_API_KEY,而非 OPEN_AI_KEY。官方 Python SDK 说明
继续使用 Chat Completions:维护 messages
每轮将模型回复保存为 assistant 消息,再追加新的 user 消息,就不用每次手写整个列表。
下面的示例从环境变量 OPENAI_MODEL 读取模型名称。请将它设为你有权限使用、且支持对应接口的模型。
import os
from openai import OpenAI
client = OpenAI()
model = os.environ["OPENAI_MODEL"]
messages = [
{
"role": "system",
"content": (
"You are a poetic assistant, skilled in explaining "
"complex programming concepts with creative flair."
),
}
]
def ask(prompt: str) -> str:
request_messages = messages + [
{"role": "user", "content": prompt}
]
completion = client.chat.completions.create(
model=model,
messages=request_messages,
)
answer = completion.choices[0].message.content or ""
messages.extend([
{"role": "user", "content": prompt},
{"role": "assistant", "content": answer},
])
return answer
print(ask("Compose a poem that explains recursion in programming."))
print(ask("What happens if recursion has no valid base case?"))
这个示例适用于普通文本对话。如果使用工具调用,还需要保存相应的工具调用和结果,只保留回复文字不够。
不想重传历史:使用 previous_response_id
如果可以改用 Responses API,可以通过 previous_response_id 续聊:第一轮保存响应,第二轮传入它的 ID 和新问题。
import os
from openai import OpenAI
client = OpenAI()
model = os.environ["OPENAI_MODEL"]
instructions = (
"You are a poetic assistant, skilled in explaining "
"complex programming concepts with creative flair."
)
first = client.responses.create(
model=model,
instructions=instructions,
input="Compose a poem that explains recursion in programming.",
store=True,
)
print(first.output_text)
second = client.responses.create(
model=model,
instructions=instructions,
previous_response_id=first.id,
input="What happens if recursion has no valid base case?",
store=True,
)
print(second.output_text)
第三轮使用 previous_response_id=second.id,之后每轮保存最新的响应 ID,供下一轮使用。服务端会据此关联之前的上下文。官方续聊示例
示例每轮都传入 instructions,因为通过 previous_response_id 续聊时,上一轮的 instructions 不会自动沿用。需要持续生效的要求,应在当前请求中再次提供。官方文本生成文档
previous_response_id 接收的是 Responses API 返回的 response.id,不能直接填入 Chat Completions 返回的 completion.id。
需要持久化会话:使用 Conversations API
如果希望为一段对话保留固定的会话 ID,可以创建 Conversation,让后续 Responses 请求引用它。下面沿用上一个示例中的 client、model 和 instructions:
conversation = client.conversations.create()
for prompt in [
"Compose a poem that explains recursion in programming.",
"What happens if recursion has no valid base case?",
]:
response = client.responses.create(
model=model,
conversation=conversation.id,
instructions=instructions,
input=[{"role": "user", "content": prompt}],
)
print(response.output_text)
应用保存 conversation.id 后,可以在后续请求中继续使用。Conversation 在服务端保存消息等会话条目,适合需要跨进程或跨设备恢复的对话。官方 Conversations 用法
历史消息是否每轮都要重新计算、重新收费?
历史消息仍是后续回答的输入上下文。即使使用 previous_response_id,链中此前的输入 token 仍会作为输入 token 计费,后续请求并非只收取新增问题的费用。官方计费说明
支持 Prompt Caching 的模型可以复用相同提示词前缀的 KV cache,减少重复的前缀处理。因此,每次重传历史,也未必需要从头计算全部内容。
缓存能否命中,取决于模型、前缀匹配、长度和缓存有效期等条件。命中后按对应模型的缓存价格计费,保存会话本身不保证缓存命中。可以查看响应 usage 中的 cached_tokens 等字段,确认实际缓存情况。官方 Prompt Caching 文档
使用时还要注意什么?
- 即使历史保存在服务端,也受模型上下文窗口限制。长对话需要适时摘要、压缩,或只保留与当前问题相关的内容。
previous_response_id依赖可用的历史响应。官方文档说明,Response 对象默认保存 30 天,因此普通响应 ID 不适合作为永久会话标识。Conversation 及其条目不受同样的 30 天 TTL 限制。官方保存期限说明- Responses 和 Conversations 示例需要 Python SDK、模型及服务端都支持相应接口。第三方兼容服务是否支持这些功能,应查阅对应服务的文档。
备注:内容仅供参考。
