调用 OpenAI 的 Chat Completions API 时,messages 数组的结构约束与角色定义往往比模型选择更能影响输出质量。messages 是对话上下文的核心载体,模型不会自动区分哪些内容是背景设定、哪些是真实用户输入,它只根据每条消息的 role 字段判断语义边界。角色定义错误会让模型把系统指令当成普通对话,或把工具结果当成用户问题。下面从请求体结构、角色边界、推理参数和调试方法四个维度展开。

一、messages 数组的结构与消息对象字段
在 Chat Completions API 的请求体中,messages 必须是一个 JSON 数组,数组中的每个元素都是一个消息对象。最基本的结构包含 role 和 content 两个字段。role 决定了该消息在对话中的身份,content 则是消息的具体文本。除此之外,消息对象还可以携带 name、tool_calls、tool_call_id 等可选字段,用于多用户区分、工具调用与结果关联。
不同角色允许携带的字段并不完全相同。例如 tool 角色必须提供 tool_call_id,用于与模型发起的某一次工具调用精确对应;assistant 角色可能会出现 tool_calls 字段,表示模型希望执行函数调用。旧版 function 角色已经被 tool 取代,新项目中应优先使用 tool。一个标准的请求体如下所示,可以看到 messages 如何承载多轮上下文。
{
"model": "gpt-4o",
"messages": [
{"role": "system", "content": "你是一个严谨的技术文档助手。"},
{"role": "user", "content": "解释一下Chat Completions API的messages字段。"},
{"role": "assistant", "content": "messages字段用于传递对话上下文,每条消息包含role和content。"},
{"role": "user", "content": "那role可以取哪些值?"}
],
"temperature": 0.7,
"max_tokens": 1000
}
需要特别留意的是,messages 数组中的顺序就是模型看到的对话顺序。OpenAI 不会自动帮你排序,也不会自动补全历史消息。如果顺序错乱,模型可能会把后出现的用户输入当成对更早内容的回应,导致上下文理解完全跑偏。因此,在构建请求前,应当把每一条消息按照真实对话发生的时间顺序依次放入数组。
二、角色定义:system、user、assistant 与 tool 的职责边界
system 角色最适合写入全局约束,例如模型身份、语气风格、输出格式、禁止事项以及任务背景。它通常放在 messages 数组的第一位,但并不强制要求必须在首位。模型对 system 内容的遵循程度较高,尤其是当指令边界清晰、表达具体时。比如“你只输出 JSON,不要输出 Markdown 代码块”会比“尽量输出结构化内容”更稳定。
user 角色表示实际用户输入。在多轮对话中,user 消息既可以是真实用户问题,也可以是后续补充说明。不要把系统指令混入每一条 user 消息中,否则会削弱 system 的权重,模型也容易把指令误认为对话内容。assistant 角色用于保存模型的历史回复,它是维持多轮一致性的关键。如果自行拼接 assistant 历史,务必保持格式与真实模型输出一致,尤其是涉及工具调用的场景。
tool 角色用于回传工具执行结果。它的 content 通常是函数返回的字符串,并且必须携带与模型先前 tool_calls 中相匹配的 tool_call_id。如果 ID 不匹配,API 会直接返回请求错误;如果遗漏 tool 消息,模型会认为工具尚未执行完成。对于 o 系列推理模型,OpenAI 还引入了 developer 角色来替代 system,因为推理模型对系统消息的处理方式不同,普通 GPT-4o 场景仍使用 system。
from openai import OpenAI
client = OpenAI()
response = client.chat.completions.create(
model="gpt-4o",
messages=[
{"role": "system", "content": "你是代码审查助手,只指出问题,不重新实现。"},
{"role": "user", "content": "请检查下面代码的问题。"},
{"role": "assistant", "content": "好的,请提供代码。"},
{"role": "user", "content": "def add(a, b): return a + b"}
],
temperature=0.2,
max_tokens=800
)
print(response.choices[0].message.content)
在实际开发中,当 assistant 消息携带 tool_calls 时,它的 content 通常为 null。随后需要插入一条 tool 消息,并在其中填入对应工具返回值。下面是一个工具调用消息结构的示例。
{
"role": "assistant",
"content": null,
"tool_calls": [
{
"id": "call_abc123",
"type": "function",
"function": {
"name": "get_weather",
"arguments": "{\"city\":\"Beijing\"}"
}
}
]
}
三、关键推理参数如何与 messages 协同工作
temperature 控制采样的随机程度。低温度适合代码生成、工具调用、格式严格输出的场景,高温度适合创意文案、头脑风暴等任务。top_p 是另一种采样控制方式,它与 temperature 不建议同时大幅调整,否则会难以判断到底哪个参数在主导输出变化。通常先固定一个,只调整另一个,便于观察效果。
max_tokens 或新版 max_completion_tokens 用于限制模型输出长度。当 messages 中的上下文非常长时,模型可用的输出空间会被压缩,因此要避免把大段无关背景塞进 system 消息。stop 参数可以设置停止序列,例如在让模型生成 JSON 时,可以使用 stop 让它在代码块结束标记处停止,避免继续输出多余解释。
presence_penalty 和 frequency_penalty 分别控制话题多样性和词汇重复度。如果希望模型更严格遵循 system 指令,不应依赖高惩罚值来压制跑题,而应把指令写得更明确。另一个重要参数是 n,它允许一次返回多个候选结果,配合 seed 可以复现输出。下面展示一个结合参数控制的请求片段。
{
"model": "gpt-4o",
"messages": [
{"role": "system", "content": "只返回 JSON 对象,不要任何解释。"},
{"role": "user", "content": "提取句子中的城市名称。"}
],
"temperature": 0,
"max_tokens": 300,
"stop": ["\n```"],
"seed": 12345
}
这些推理参数并不是独立生效的,它们与 messages 中的上下文质量共同决定最终输出。如果角色定义混乱,即便把 temperature 调到 0,模型仍然可能在错误的理解路径上生成稳定但错误的内容。因此,先保证消息结构正确,再去调优采样参数,才能获得可靠效果。
四、常见误区与调试技巧
一个常见误区是把 system 角色写成用户发言,例如将“你是一个翻译助手”放进 user 消息。模型虽然可能理解这句话,但不会像 system 那样持续遵循,后续对话中更容易跑偏。另一个误区是在手动拼接 assistant 历史时,伪造不存在的 tool_calls 或遗漏 tool_call_id,这会导致 API 直接报错,或让模型误以为工具还没执行。
还有开发者忽略 messages 的顺序,把最新问题放在数组开头,或者多轮拼接时漏掉早期上下文。模型会严格按照数组顺序阅读,顺序混乱会破坏逻辑。此外,system 消息写得过长也会挤占输出空间,尤其在 max_tokens 较小的情况下,模型可能来不及给出完整回答。
调试时建议在每次请求前打印完整 messages 数组,确认每条消息的 role 和 content 符合预期。如果输出格式不稳定,可以先固定 seed 与 temperature,再逐步简化角色定义。下面是一个错误示范,它把系统指令和用户输入都放在了 user 角色中。
[
{"role": "user", "content": "你是翻译助手。"},
{"role": "user", "content": "翻译:hello"}
]
这个结构虽然可能得到回答,但模型对“你是翻译助手”的遵循度会明显下降。修正方法是将第一句移到 system 角色中,让身份设定与真实输入分离。调试的目标不是让单次输出看起来正确,而是让模型在多次请求中行为可预期、上下文不混乱。
Chat Completions APImessages结构角色定义修改时间:2026-08-21 12:00:18