AI 工具

xAI Grok Chat Completions:兼容旧版接口的 Python 客户端

兼容 OpenAI 风格的 POST /v1/chat/completions 仍可用,但 xAI 文档将其视为 legacy。这篇演示最小客户端,并在 stderr 提示迁移。

BINBHO · 图文教程

兼容 OpenAI 风格的 POST /v1/chat/completions 仍可用,但 xAI 文档将其视为 legacy。这篇演示最小客户端,并在 stderr 提示迁移。

先看清成品

  • mock 回显消息。
  • 官方无效密钥 HTTP 400。
  • 1 项 pytest 通过。

合规提醒:Grok 可结合网页与 X 平台实时信息,实时内容未必准确,重要结论须核对可靠来源。API 密钥在 console.x.ai 管理,勿提交公开仓库。遵守 xAI 使用政策;勿用于骚扰、欺诈或未授权自动化。价格与套餐以官网为准。

运行说明:无正式 XAI_API_KEY。A/B 用 mock;对官方 api.x.ai 无效密钥实测 HTTP 400(Incorrect API key)。C 校验 search_parameters。Chat Completions 在文档中标为 legacy,新集成优先 Responses。2026-10-23(Asia/Shanghai)pytest 通过。

做完之后你会得到什么?

成果 说明
grok_chat.py legacy 客户端
mock_xai.py 假服务
test_105a.py 测试

需要准备哪些环境?

项目 说明
Python 3.10+
环境变量 XAI_API_KEY;可选 XAI_BASE_URL / XAI_MODEL

关键事实

事实 来源
主机多为 https://api.x.ai,Bearer 认证 官方 Docs
Chat Completions 标为 legacy;新项目优先 Responses 官方 Chat Completions 指南
模型 ID 以控制台/文档为准(示例 grok-4 需按账号可见性调整) 实践

文件结构

105-grok-A-src/
├── grok_chat.py
├── mock_xai.py
└── test_105a.py

第 1 步:客户端

#!/usr/bin/env python3
"""xAI Grok chat completions client (legacy endpoint; prefer Responses for new apps)."""
from __future__ import annotations
import argparse, json, os, sys, urllib.error, urllib.request

def chat(base, key, model, message, system=None):
    msgs = []
    if system:
        msgs.append({"role": "system", "content": system})
    msgs.append({"role": "user", "content": message})
    req = urllib.request.Request(
        f"{base.rstrip('/')}/v1/chat/completions",
        data=json.dumps({"model": model, "messages": msgs}).encode(),
        headers={"Authorization": f"Bearer {key}", "Content-Type": "application/json"},
        method="POST",
    )
    try:
        with urllib.request.urlopen(req, timeout=120) as resp:
            return json.loads(resp.read().decode())
    except urllib.error.HTTPError as e:
        raise SystemExit(f"HTTP {e.code}: {e.read().decode(errors='replace')}") from e

def main(argv=None) -> int:
    ap = argparse.ArgumentParser(description=__doc__)
    ap.add_argument("message", nargs="?", default="用一句话介绍 Grok")
    ap.add_argument("--base", default=os.environ.get("XAI_BASE_URL", "https://api.x.ai"))
    ap.add_argument("--key", default=os.environ.get("XAI_API_KEY", ""))
    ap.add_argument("--model", default=os.environ.get("XAI_MODEL", "grok-4"))
    args = ap.parse_args(argv)
    if not args.key:
        print("设置 XAI_API_KEY", file=sys.stderr); return 2
    r = chat(args.base, args.key, args.model, args.message)
    print(r["choices"][0]["message"]["content"])
    print("# note: Chat Completions is legacy; new integrations should use /v1/responses", file=sys.stderr)
    return 0

if __name__ == "__main__":
    raise SystemExit(main())

第 2 步:Mock

#!/usr/bin/env python3
"""Mock xAI chat completions + responses."""
from __future__ import annotations
import json
from http.server import BaseHTTPRequestHandler, HTTPServer

API_KEY = "xai-test-key"

class H(BaseHTTPRequestHandler):
    def log_message(self, *a):
        pass
    def _auth(self):
        return self.headers.get("Authorization") == f"Bearer {API_KEY}"
    def _send(self, code, obj):
        b = json.dumps(obj).encode()
        self.send_response(code)
        self.send_header("Content-Type", "application/json")
        self.send_header("Content-Length", str(len(b)))
        self.end_headers()
        self.wfile.write(b)
    def do_POST(self):
        if not self._auth():
            self._send(401, {"error": "Unauthorized"}); return
        n = int(self.headers.get("Content-Length", 0))
        body = json.loads(self.rfile.read(n) or b"{}")
        if self.path == "/v1/chat/completions":
            msgs = body.get("messages") or []
            last = ""
            for m in msgs:
                if m.get("role") == "user":
                    last = m.get("content") or last
            model = body.get("model") or "grok-4"
            self._send(200, {
                "id": "chatcmpl-xai",
                "model": model,
                "choices": [{"index": 0, "message": {"role": "assistant", "content": f"[legacy-chat] {last[:160]}"}, "finish_reason": "stop"}],
            })
            return
        if self.path == "/v1/responses":
            # simplified Responses API shape
            inp = body.get("input") or body.get("messages") or ""
            if isinstance(inp, list):
                text = ""
                for m in inp:
                    if isinstance(m, dict) and m.get("role") == "user":
                        c = m.get("content")
                        text = c if isinstance(c, str) else str(c)
            else:
                text = str(inp)
            model = body.get("model") or "grok-4"
            self._send(200, {
                "id": "resp_mock",
                "model": model,
                "output": [{"type": "message", "content": [{"type": "output_text", "text": f"[responses] {text[:160]}"}]}],
                "output_text": f"[responses] {text[:160]}",
            })
            return
        self.send_error(404)

def main():
    import argparse
    ap = argparse.ArgumentParser(); ap.add_argument("--port", type=int, default=9105)
    args = ap.parse_args()
    HTTPServer(("127.0.0.1", args.port), H).serve_forever()

if __name__ == "__main__":
    main()

第 3 步:测试

import subprocess, sys, time
from pathlib import Path
HERE = Path(__file__).resolve().parent

def test_chat():
    port = 19150
    proc = subprocess.Popen([sys.executable, str(HERE/"mock_xai.py"), "--port", str(port)], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
    try:
        time.sleep(0.3)
        r = subprocess.run([sys.executable, str(HERE/"grok_chat.py"), "hello grok", "--base", f"http://127.0.0.1:{port}", "--key", "xai-test-key"], capture_output=True, text=True, check=True)
        assert "hello grok" in r.stdout
        assert "legacy" in r.stderr.lower()
    finally:
        proc.terminate(); proc.wait(timeout=5)

第 4 步:运行

$ pytest grok chat mock
.                                                                        [100%]
1 passed in 0.36s

$ invalid key api.x.ai
HTTP 400
{"code":"invalid-argument","error":"Incorrect API key provided. You can obtain an API key from https://console.x.ai."}

怎么检查结果?

  • pytest 绿。
  • stderr 含 legacy 提示。

调试示例

Incorrect API key。 打开 console.x.ai 创建密钥;确认环境变量无引号/空格。400 与部分厂商的 401 不同,以响应 JSON 为准。

常见问题

现象 原因 处理
还要用 chat? 兼容旧代码 新代码走 B 篇
模型 404 ID 变更 对照控制台
流式 按文档 stream 本篇未演示

模板规则

  • 密钥保密。
  • legacy 接口勿作为新架构默认。
  • 输出需核对。

项目资源

用途 地址
Chat Completions 指南(官方) https://docs.x.ai/docs/guides/chat-completions
API Reference(官方) https://docs.x.ai/docs/api-reference
Console https://console.x.ai/

能跑通 legacy,接着迁到 Responses。