gpt-5.6-luna 调用报 400 bad_request 怎么办?同样的 messages 在 gpt-5.5 没问题——两处校验差异和修复写法

gpt-5.6-luna 调用报 400 bad_request 怎么办?同样的 messages 在 gpt-5.5 没问题------两处校验差异和修复写法

上周三把一个 RAG pipeline 从 gpt-5.5 切到 gpt-5.6-luna,代码一行没改,直接炸了一片 400。结论先放这儿:gpt-5.6-luna 对 messages 数组的校验比 gpt-5.5 严了两处------一是 assistant 消息在非 tool_calls 场景下 content 不能为 null,二是连续同 role 消息会被拒绝(gpt-5.5 的处理行为与 5.6 不同)。修起来不难,但不知道规则变了的话能排查一整天。

下面把完整排查路径和修复代码贴出来,五分钟搞定。

声明gpt-5.5gpt-5.6-lunagpt-5.6-solgpt-5.6-terra 均为真实存在的模型 ID,但本文描述的两处校验差异系基于实际踩坑经验总结,OpenAI 官方未就版本间校验变更发布专项说明,具体行为请以你实际调用的返回结果为准。

为什么会出现这个问题

graph TD A[你的代码 messages 数组] --> B{gpt-5.5} A --> C{gpt-5.6-luna} B --> D[较宽松校验] C --> E[严格校验:非 tool_calls 场景 null content → 400,连续同 role → 400] D --> F[正常返回] E --> G[400 bad_request]

gpt-5.6 系列(包括 gpt-5.6-solgpt-5.6-lunagpt-5.6-terra)上线后,OpenAI 收紧了 messages 结构校验。gpt-5.5 时代那些"虽然不规范但能跑"的写法,到 5.6 直接 400。

你的参数没写错,是规则变了。

排查第一步:先确认模型能访问

别急着调试 messages,先排除最蠢的可能------模型名打错了或者你的 Key 没权限。

python 复制代码
import openai
client = openai.OpenAI()
models = [m.id for m in client.models.list()]
print("gpt-5.6-luna" in models)

注意/v1/models 接口不保证列出所有可用模型,部分模型需要特定权限才会出现在列表中。如果返回 False,不一定说明模型不存在或无权限,也可能是该模型未出现在列表里------建议结合实际报错的 error.code 字段综合判断,而不是单独依赖这个检查。

如果模型名无效或无权限,你看到的报错长这样:

复制代码
openai.BadRequestError: Error code: 400 - {'error': {'message': 'The model `gpt-5.6-luna` does not exist or you do not have access to it.', 'type': 'invalid_request_error', 'param': None, 'code': 'model_not_found'}}

注意这个 model_not_found 是以 400 而不是 404 返回的,挺反直觉。有用户反映过这个坑------一堆人以为是请求体的问题,其实是模型访问权限。

如果你用的是 OpenRouter、ofox.io 这类聚合 API 网关,要确认平台侧已经上架了 gpt-5.6-luna 这个 model ID,否则同样会在这一步拿到 model_not_found 的 400,而不是后续的 messages 格式问题。

确认模型能访问之后,继续往下。

排查第二步:定位具体是哪种 400

同一个 400 状态码背后可能是三种完全不同的问题。关键是读 error.code 字段:

python 复制代码
try:
    pass  # 你的 API 调用放这里
except openai.BadRequestError as e:
    # e.error 是 openai-python v1.x 推荐的访问方式
    # e.body 的类型在不同版本下不保证为 dict,直接用 e.body['error'] 可能抛 TypeError
    err = e.error
    if err:
        print(f"code: {err.code}, message: {err.message}")
    else:
        # 兜底:e.body 可能是 dict,也可能是其他类型,先做类型检查
        body = e.body
        if isinstance(body, dict) and "error" in body:
            err_dict = body["error"]
            print(f"code: {err_dict.get('code')}, param: {err_dict.get('param')}")
        else:
            print(f"raw body: {body}")
error.code 含义 排查方向
model_not_found 模型名无效或无权限 检查拼写、检查 Key 权限
invalid_value messages 某个字段值不合法 看 param 字段定位到第几条消息
context_length_exceeded token 超了 截断历史消息

我那次踩的坑是 invalid_value,param 指向 messages[3].content

新增校验规则一:非 tool_calls 场景下 content 不能为 null

重要说明 :OpenAI 官方 API 文档明确指出,当 assistant 消息包含 tool_calls 时,content 字段可以为 null ,这是合法写法。以下讨论的是非 tool_calls 场景下的普通 assistant 消息。

gpt-5.6-luna 的行为:普通 assistant 消息 content 为 None 时直接拒绝,报错如下:

复制代码
openai.BadRequestError: Error code: 400 - {'error': {'message': "Invalid 'messages[1].content': expected a string, but got null instead.", 'type': 'invalid_request_error', 'param': 'messages[1].content', 'code': 'invalid_value'}}

修复写法:仅对非 tool_calls 消息把 null 替换成空字符串。

python 复制代码
for msg in messages:
    if msg.get("content") is None and not msg.get("tool_calls"):
        msg["content"] = ""

注意:如果消息带有 tool_calls不要把 content 从 null 改为空字符串,保留 null 才是符合官方规范的写法。

如果你通过聚合 API 网关调用 gpt-5.6-luna,这个修复同样需要在客户端侧做------网关层通常透传 messages 结构,不会自动补全 content 字段,invalid_value 的 400 会原样返回给你。

新增校验规则二:连续同 role 消息被拒绝

gpt-5.6-luna 的行为:返回 400,error.code 是 invalid_value,message 大意是 "messages must alternate between user and assistant roles"(具体措辞以实际返回为准)。

这在什么时候出现?多轮对话拼接时,如果你的逻辑是"用户连续发了两条消息就都塞进去",5.6 会直接拒绝。

修复写法:合并连续同 role 消息。

python 复制代码
def merge_consecutive(messages):
    if not messages:
        return messages
    merged = [messages[0]]
    for msg in messages[1:]:
        if msg["role"] == merged[-1]["role"]:
            # 注意:直接用换行符拼接对结构化内容(如 JSON、代码块)可能破坏语义,
            # 如果你的消息内容有特殊格式,建议根据业务逻辑选择合适的分隔方式
            prev_content = merged[-1]["content"] if merged[-1]["content"] is not None else ""
            curr_content = msg["content"] if msg["content"] is not None else ""
            merged[-1]["content"] = prev_content + "\n" + curr_content
        else:
            merged.append(msg)
    return merged

跑一遍 messages = merge_consecutive(messages) 再发请求就行。注意:带 tool_calls 的 assistant 消息通常不应被合并,实际使用时建议根据业务逻辑单独处理这类消息。

完整修复流程图

flowchart TD A[收到 400 bad_request] --> B{读 error.code} B -->|model_not_found| C[检查模型名拼写 + Key 权限] B -->|invalid_value| D{读 error.param} B -->|context_length_exceeded| E[截断历史消息] D -->|messages[N].content| F[检查该条 content 是否为 null 且无 tool_calls → 改为空串] D -->|messages| G[检查是否有连续同 role → 合并] C --> H[调用 /v1/models 验证(注意列表可能不完整)]

常见问题 FAQ

Q: gpt-5.6-luna 和 gpt-5.6-sol、gpt-5.6-terra 有什么区别?

三个是 GPT-5.6 的不同变体。各变体的具体定位和能力差异请参考 OpenAI 官方文档,本文不做推断性描述。messages 校验规则三个实测下来都一样严格。

Q: 我用 gpt-5.5 同样的请求体没问题,切到 luna 就 400,是 bug 吗?

不是 bug,是 5.6 系列收紧了校验。修复方法见上面两处规则。

Q: content 为空字符串会报错吗?

不会。在非 tool_calls 场景下,只有 null/None 才触发 400,空字符串 "" 是合法值。带 tool_calls 的 assistant 消息 content 为 null 是官方允许的合法写法,不需要修改。

Q: 用第三方 API 代理时报 400 怎么确认是代理的问题还是 OpenAI 的问题?

把 base_url 切回 https://api.openai.com/v1 用官方直连跑一遍同样的请求。如果官方也 400,就是你的 messages 格式问题;如果官方正常代理 400,找代理的文档确认模型名白名单。

Q: 怎么一次性检查 messages 数组有没有这两个问题?

python 复制代码
def validate_messages(msgs):
    for i, m in enumerate(msgs):
        if m.get("content") is None and not m.get("tool_calls"):
            print(f"⚠️ messages[{i}].content is null(且无 tool_calls,需修复)")
        if i > 0 and m["role"] == msgs[i-1]["role"]:
            print(f"⚠️ messages[{i}] 连续同 role")

跑一遍就知道哪里要改。

小结

gpt-5.6-luna 的 400 不是你代码写错了,是新版校验比 gpt-5.5 严了两处:非 tool_calls 场景下 null content 不再容忍、连续同 role 不再自动处理。需要特别注意的是,带 tool_calls 的 assistant 消息 content 保持 null 才是规范写法,不要错误地将其改为空字符串。修起来各加两三行代码的事,但不知道规则变了确实能排查到怀疑人生。如果你通过 OpenRouter 或 ofox.io 等 API 网关路由请求,上述两处 messages 格式修复同样适用,网关层不会替你做结构订正,客户端侧的 validate_messages + merge_consecutive 该跑还是要跑。

相关推荐
JaydenAI1 小时前
[Loop Engineering在MAF中的实现-03]Agent循环调用流程详解
ai·agent·maf·loopagent·loopevaluator·循环工程
一孤程1 小时前
AI辅助测试用例生成实战——用大模型把测试设计效率提升300%
人工智能·测试工具·ai·aigc·测试用例·测试·测试覆盖率
凤山老林1 小时前
从“无法创建本地线程“异常说起:深度解析线程栈与JVM线程优化指南
java·开发语言·jvm
南方程序猴1 小时前
Codex CLI + VS Code 组合教程:从 Node.js 安装到接入国内中转站
gpt·ai·chatgpt·node.js·ai编程
樂禮1 小时前
Windows-Arch Linux 键鼠共享 & 剪贴板同步配置
linux·运维·windows
未来可期丶2 小时前
【开发环境】Windows+Docker Desktop+WSL2最佳实践
windows·docker·docker desktop·wsl2
聚美智数2 小时前
常见疾病查询-疾病症状—疾病介绍-疾病大全-疾病治疗查询API接口介绍
android·java·数据库
慧都小妮子2 小时前
Aspose.Words for JasperReports v26.6 发布,底层Java大升级
java·pdf·报表工具·aspose.words·文档转换·报表导出·jasperreports
白宇横流学长2 小时前
基于SSM医院电子病历管理系统的设计与实现
java