2.5 ChatCompletion API多轮对话实现与消息结构详解

2 阅读6分钟

2.5 ChatCompletion API多轮对话实现与消息结构详解

一、ChatCompletion与消息结构

ChatCompletion API是当前调用GPT模型的主流方式。其核心是messages数组,通过systemuserassistant三种角色组织对话上下文,实现多轮对话与角色设定。


二、消息角色说明

角色说明典型用途
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可能超出模型上下文限制。常用策略:

  1. 滑动窗口:只保留最近N轮
  2. 摘要压缩:将早期对话用模型摘要后替换
  3. 关键信息提取:只保留对后续有用的片段
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差异对比与选型