2.5 ChatCompletion API多轮对话实现与消息结构详解
一、ChatCompletion与消息结构
ChatCompletion API是当前调用GPT模型的主流方式。其核心是messages数组,通过system、user、assistant三种角色组织对话上下文,实现多轮对话与角色设定。
二、消息角色说明
| 角色 | 说明 | 典型用途 |
|---|---|---|
| system | 系统级设定 | 角色、风格、规则、约束 |
| user | 用户输入 | 问题、指令、上传内容 |
| assistant | 模型历史回复 | 多轮对话中的历史回答 |
注意:部分模型对system支持有限,可退化为在首条user消息中嵌入系统指令。
三、多轮对话实现
3.1 核心逻辑
每次用户发言时,将user消息追加到messages;调用API后,将assistant回复也追加到messages。下次请求时,整个messages作为上下文发送,模型即可"记住"之前的对话。
sequenceDiagram
participant U as 用户
participant M as messages
participant A as API
U->>M: 第1轮: user "什么是Python?"
M->>A: 发送 [system, user]
A->>M: 返回 assistant 回复
M->>M: 追加 assistant
U->>M: 第2轮: user "它有什么优点?"
M->>A: 发送 [system, user1, asst1, user2]
A->>M: 返回 assistant 回复(基于上下文)
3.2 完整代码示例
"""
多轮对话完整实现
"""
import os
from dotenv import load_dotenv
from openai import OpenAI
load_dotenv()
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
# 初始化对话
messages = [
{"role": "system", "content": "你是一个Python编程助手,回答简洁专业。"}
]
def chat(user_input: str) -> str:
messages.append({"role": "user", "content": user_input})
response = client.chat.completions.create(
model="gpt-3.5-turbo",
messages=messages,
temperature=0.5,
max_tokens=500
)
reply = response.choices[0].message.content
messages.append({"role": "assistant", "content": reply})
return reply
# 测试多轮
if __name__ == "__main__":
r1 = chat("什么是列表推导式?")
print("AI:", r1)
r2 = chat("能再举一个例子吗?") # 依赖上文
print("AI:", r2)
四、上下文长度管理
当对话轮次增多,messages可能超出模型上下文限制。常用策略:
- 滑动窗口:只保留最近N轮
- 摘要压缩:将早期对话用模型摘要后替换
- 关键信息提取:只保留对后续有用的片段
def trim_messages(msgs: list, max_tokens_approx: int = 4000) -> list:
"""简化版:只保留system + 最近若干轮"""
result = [msgs[0]] # 保留system
total = 0
for m in reversed(msgs[1:]):
total += len(m.get("content", "")) // 2 # 粗略估算
if total > max_tokens_approx:
break
result.insert(1, m)
return result
五、system消息的最佳实践
5.1 system vs user
- system:定义角色、规则、风格,通常较长且固定,每次请求都会发送
- user:用户输入,可变
- 若模型对system支持有限,可将系统指令放在首条user中,如"【系统】你是翻译专家。用户:请翻译xxx"
5.2 system长度
system过长会占用大量Token。建议精简到100-300字,保留核心约束。详细规则可放在外部文档,需要时通过RAG检索注入。
5.3 多轮对话中是否重复发送system
是的。每次请求的messages应包含完整上下文,即[system, user1, assistant1, user2, assistant2, ...]。系统不会自动记忆,需由客户端维护。
六、流式输出与多轮对话
流式输出时,需将本次assistant的完整回复追加到messages后再发起下一轮,否则下一轮会缺少上一轮的assistant消息。示例:
full_reply = ""
for chunk in stream:
if chunk.choices[0].delta.content:
full_reply += chunk.choices[0].delta.content
messages.append({"role": "assistant", "content": full_reply}) # 必须追加
七、常见错误
| 错误 | 原因 | 解决 |
|---|---|---|
| 模型"忘记"上文 | messages未包含历史 | 每次请求携带完整messages |
| 角色混乱 | system被覆盖或丢失 | 确保messages[0]为system |
| 超长报错 | 上下文超限 | 截断或摘要历史消息 |
八、与《大模型应用开发极简入门》第2.5节的对应
本书第2章「使用GPT-4和ChatGPT」专门讲解 ChatCompletion API 的核心用法:messages 结构、system/user/assistant 角色、多轮对话,并给出代码示例(聊天机器人、问答系统)。本节与之一一对应:消息角色说明对应「三种角色」,多轮对话实现与代码对应「多轮对话与代码示例」,上下文长度管理对应生产中的「长对话处理」,system 最佳实践对应「角色设定」的落地。掌握本节即可按书中建议实现聊天机器人与问答系统。
九、消息格式的边界情况
9.1 content 类型
多数情况下 content 为字符串。在多模态模型(如 gpt-4o)中,content 可为数组,包含 type: "text" 与 type: "image_url" 等块,用于图文混合输入。纯对话场景仅需文本即可。
9.2 空 content
避免发送空字符串的 user 消息,否则可能触发 API 校验或得到无意义回复。若需「仅用 system 约束」的回合,可保留上一轮 assistant 后追加一条极短 user(如「继续」)或由业务逻辑决定是否发起请求。
9.3 超长单条消息
单条消息过长会占用大量 Token。可要求用户分条输入,或在后端对过长内容做摘要后再放入 user。
十、多轮对话与 Function Calling 的配合
若后续使用 Function Calling,多轮对话的 messages 中会插入 role: "function" 的消息(见 2.9 节)。多轮 + 函数调用的顺序示例:[system, user1, assistant1, user2, function_call, function_result, assistant2, ...]。维护 messages 时只需按时间顺序追加,无需特殊处理角色类型。
十一、会话持久化与多端同步(与第3章衔接)
在实际产品中,对话往往需要持久化(存数据库或 Redis)以便用户刷新或换设备后仍能继续。做法是:为每个会话分配唯一 session_id,每次请求时根据 session_id 加载该会话的最近 N 条 messages,调用 API 后将新产生的 user 与 assistant 消息写回存储。书中第 3 章「对话能力集成」会详细讲解多轮对话管理与上下文持久化,本节掌握单次请求内的 messages 结构即可与之衔接。多端同步时,以服务端存储的 messages 为准,各端只发送当前用户输入并展示服务端返回的 assistant 回复。
十二、流式输出时的 messages 维护(复习)
流式输出(stream=True)时,客户端会逐 chunk 收到 assistant 的 content,需在内存中拼接成完整字符串后,再将该条 {"role": "assistant", "content": full_reply} 追加到 messages,然后才能发起下一轮请求。若未追加就发起下一轮,模型会缺少上一轮助手回复,导致「忘记」上文。书中 2.5 节强调的多轮对话与消息结构,在流式场景下同样适用,仅多一步「先拼接再追加」即可。
十三、小结
掌握 messages 结构与多轮追加逻辑,即可实现完整的对话式应用。注意上下文长度与成本控制,在长对话场景下做好截断或摘要。下一节将对比 Completion 与 ChatCompletion API。
十四、与书中代码模板的对应
书中「核心代码模板速查手册」中的 gpt_multi_turn_dialog 即多轮对话模板:维护 dialog_messages 列表,每次将 user 输入追加后调用 API,再将返回的 assistant 内容追加回列表。本节「多轮对话实现」中的 chat() 与 messages.append 逻辑与之一致,仅封装方式不同。流式输出时的「先拼接 full_reply 再追加」与书中流式用法一致。将本节示例与书中模板对照,即可在任意项目中实现与书中建议一致的多轮对话与消息结构。与 2.6 节的关系:多轮对话必须使用 ChatCompletion,不能使用 Completion;与 2.7 节的关系:多轮会导致单次请求 Token 随轮次增长,需在成本控制与上下文长度间权衡。
下一节预告:2.6 Completion API与ChatCompletion API差异对比与选型