codecamp

Grok 两代接口字段对照

最容易让迁移卡住的不是「新接口怎么写」,而是「旧接口的心智还没换过来」。不少团队把 OpenAI 或旧教程里的 messages + choices[0].message.content 直接搬过来,改个模型名就发出去。结果模型确实回了内容,可代码在错误的路径上取答案,拿到 None 或空字符串,再往上层传。这一节把两套写法的字段对照讲清,并给一个能离线判定的接口识别器。

官方 Quickstart 给出的首请求写法是 client.responses.create(...),入参是 input,取答案用 SDK 对象的 output_text(详见 2.1)。本节只处理「字段层面两套写法怎么对上」,不处理端点废弃与否——那是另一个待核问题。

一、问题不是「字段名不同」,而是「取值路径不同」

两套写法最核心的区别有两点:请求里用户内容放在哪,响应里答案从哪取。

  • 旧写法(Chat Completions 形态):请求用 messages(role/content 数组),响应用 choices[0].message.content。
  • 新写法(Responses 形态):请求用 input(字符串或数组),答案可取 SDK 的 output_text,或原始响应里的 output[].content。

这两组词不要混着记。混用最典型的后果不是报错,而是代码读到了错误的位置,拿到空值。注意:本节只面向 Responses 写法的 SDK 返回值解析;REST 原始响应结构以官方 API 参考为准,不在本节臆测。

二、最小可运行示例:离线接口识别器

下面的脚本不联网、不需要真实密钥。它把「这份请求体属于哪套写法」「两套写法能不能归一化」变成可以断言的函数。

"""接口识别器:判断一个请求体属于哪套写法,并给出迁移提示。"""

from typing import Any

def classify_interface(body: dict) -> str:
    """看请求体顶层键,判断接口写法。"""
    keys = set(body.keys())
    if "messages" in keys and "input" not in keys:
        return "chat_completions"
    if "input" in keys and "messages" not in keys:
        return "responses"
    if "messages" in keys and "input" in keys:
        return "mixed"  # 两套字段都出现,几乎一定是写错了
    return "unknown"

def response_style(body: dict) -> str:
    """看响应取值路径,判断解析层配对情况。"""
    if "choices" in body and "output" not in body:
        return "chat_completions"
    if "output" in body and "choices" not in body:
        return "responses"
    if "choices" in body and "output" in body:
        return "mixed"
    return "unknown"

def normalize_messages_to_input(messages: list[dict]) -> list[dict]:
    """把 chat completions 的 messages 映射成 responses 的 input 数组。
    仅覆盖 role/content 文本项;工具、图像等多模态项需另行处理。"""
    out = []
    for m in messages:
        out.append({"role": m["role"], "content": m["content"]})
    return out

def offline_check() -> None:
    old_body = {"model": "grok-4.7", "messages": [{"role": "user", "content": "你好"}]}
    new_body = {"model": "grok-4.7", "input": "你好"}
    assert classify_interface(old_body) == "chat_completions"
    assert classify_interface(new_body) == "responses"
    mapped = normalize_messages_to_input(old_body["messages"])
    assert mapped == [{"role": "user", "content": "你好"}], mapped
    print("离线自检通过")
    print("old ->", classify_interface(old_body))
    print("new ->", classify_interface(new_body))
    print("mapped ->", mapped)

if __name__ == "__main__":
    offline_check()

真正上线时,把归一化逻辑换成 SDK 调用只需要几行;上面的 classify_interface 仍可作为 CI 里的一道护栏,拦住「旧字段混进新请求体」这类回归。

三、两套写法的字段对照(区分已核验与待核)

维度 旧写法(Chat Completions 形态) 新写法(Responses 形态) 核验状态
请求用户输入 messages(role/content 数组) input(字符串或数组) 新写法以 Quickstart 为准;旧写法为对比项
答案取值 choices[0].message.content SDK output_text / 原始 output[].content 新写法以 Quickstart 为准
端点 /v1/chat/completions /v1/responses 以官方 API 参考为准

三件要强调的事:

  • input 和 messages 不是同一个字段的不同拼写,它们属于两套接口写法。本节统一用 input,是为了让后续讨论多轮状态、reasoning.encrypted_content 时建立在同一套心智上。
  • 解析层要和调用层配对。用 SDK 就按 SDK 的属性取,直接用 REST 就按官方 API 参考里的响应结构取;两边混用是「明明有响应却拿不到答案」的高频原因(详见 2.1 的 extract_answer)。
  • 不要宣称旧端点已废弃。截至目前,本教程未在官方页直接确认 Chat Completions 的废弃状态,「已废弃」目前只见于第三方指南。若沿用旧端点,请到官方页确认当前支持状态;本节只做字段对照,不下端点生死的结论。

四、预期输出与异常分支

运行离线识别器,预期输出:

离线自检通过
old -> chat_completions
new -> responses
mapped -> [{'role': 'user', 'content': '你好'}]

换成真实调用后,返回值应是一段文本答案。若出现异常,按下面顺序定位,不要一上来就改请求体:

现象 优先怀疑 第一步动作
mixed 判定 请求体两套字段都出现 删掉不属于目标写法的那组键
有响应但取值为空 解析层与调用层不配对 先打印响应类型,再决定用哪个属性
401 密钥没读到或无效 核对环境变量读取时机
404 端点路径写错 对照官方 API 参考确认当前路径

最后一行尤其重要:None 往往不是模型没回答,而是你在错误的对象上找答案。把 classify_interface 和 normalize_messages_to_input 单独抽成函数,就是为了能各写一条断言,把这个错误挡在离线阶段。

五、判据卡

判断项 该这么做 不该这么做
选写法 新项目直接用 /v1/responses + input 从旧教程复制 messages 写法
取答案 用与调用层配对的解析函数 在 SDK 对象上找 REST 字段名
迁移验证 两类写法归一化后做等价断言 靠真实额度反复试错
端点状态 沿用旧端点前到官方页确认 照第三方说法写「已废弃」
出错排查 先判定接口写法是否混用 先改请求体碰运气

这张卡的重点是先判定写法是否混用。四类现象里「取值为空」和「mixed」常常同源:请求体里混了字段,解析层又去错误的位置取,表现都是空值,但修法完全不同。

六、来源与核验

  • 官方 Quickstart:https://docs.x.ai/developers/quickstart(核验日期 2026-11-04)。client.responses.create、input、output_text 与 /v1/responses 写法均以该页为准。
  • 官方 API 参考(含 Chat Completions 端点的当前状态):https://docs.x.ai/developers/rest-api-reference。本节未逐页抓取该参考的具体字段,REST 响应结构与端点状态以官方页当前显示为准。
  • 「Chat Completions 已标 legacy / 已废弃」目前只见于第三方指南,官方页未在本次核验中直接确认,本教程不作事实陈述;若沿用旧端点,请到官方页确认当前支持状态。
  • 价格、额度与区域可用性以控制台当前页面为准。

七、动手试

  1. 给 classify_interface() 补一条用例:当一个请求体同时出现 messages 和 input 时,断言它返回 mixed 而不是静默选其一。想一想:生产代码里这种请求体应该怎么处理,直接拒绝还是择一采用?
  2. 把 normalize_messages_to_input() 扩成能处理 assistant 角色的历史,再写一个反向函数,把 input 数组还原成 messages,验证往返之后结构一致。
  3. 在 CI 里用 response_style() 检查一次真实响应,当解析层声明用 Responses 却出现 choices 键时告警。写一条你预期会触发告警的真实响应样本(mock 即可)。
Grok 首请求失败排查树
Grok 多轮上下文怎么接
温馨提示
下载编程狮App,免费阅读超1000+编程语言教程
取消
确定
目录

关闭

MIP.setData({ 'pageTheme' : getCookie('pageTheme') || {'day':true, 'night':false}, 'pageFontSize' : getCookie('pageFontSize') || 20 }); MIP.watch('pageTheme', function(newValue){ setCookie('pageTheme', JSON.stringify(newValue)) }); MIP.watch('pageFontSize', function(newValue){ setCookie('pageFontSize', newValue) }); function setCookie(name, value){ var days = 1; var exp = new Date(); exp.setTime(exp.getTime() + days*24*60*60*1000); document.cookie = name + '=' + value + ';expires=' + exp.toUTCString(); } function getCookie(name){ var reg = new RegExp('(^| )' + name + '=([^;]*)(;|$)'); return document.cookie.match(reg) ? JSON.parse(document.cookie.match(reg)[2]) : null; }