很多团队在切换 API 提供商时,第一反应是把代码里的 api_key 换掉,然后跑测试。如果一切正常,皆大欢喜;如果出现 401、404,就开始凭经验猜测问题。鉴权环节并不是“一个字符串”那么简单。base_url、Authorization 头格式、key 的携带方式、错误响应结构,任何一个不匹配,请求都可能直接失败。

这篇文章不预设任何目标 API 的参数与 OpenAI 相同,也不替任何 API 提供商下结论。它提供一套可执行的迁移验证清单,你可以在真实环境中逐项确认,把“我以为兼容”变成“我验证过兼容”。如果目标 API 是 DeepSeek,请务必先从 DeepSeek 官方文档确认 base_url、路径、模型名等关键值,再按清单验证。

为什么“只换 key”不够

一次 API 请求能成功,至少需要四个要素同时成立:

  • 请求发往正确的地址(base_url)
  • 请求头携带服务端认可的鉴权信息
  • 请求体结构符合服务端预期
  • 服务端响应的结果能被客户端正确解析

这四个要素在 OpenAI API 中成立,不代表在另一个 API 中也成立。即使对方声明“兼容 OpenAI”,兼容的粒度也可能因版本、路径或字段而异。迁移时把每一层都当作“未知”去验证,是成本最低的策略。

先做一次最小请求,而不是直接改业务代码

迁移的第一步,是绕过业务代码,用最简单的方式向目标 API 发一次真实请求。这样可以隔离问题,避免把鉴权错误和业务逻辑错误混在一起。

下面是一个不依赖任何 SDK 的 Python 示例,使用 requests 库直接构造请求:

import os
import requests

base_url = os.environ.get("TARGET_BASE_URL")
api_key = os.environ.get("TARGET_API_KEY")

# 这个路径和请求体仅用于连通性验证,具体以目标 API 文档为准
resp = requests.post(
    f"{base_url}/chat/completions",
    headers={
        "Authorization": f"Bearer {api_key}",
        "Content-Type": "application/json",
    },
    json={
        "model": "test-model",
        "messages": [{"role": "user", "content": "hello"}],
    },
    timeout=30,
)

print(resp.status_code)
print(resp.text)

运行前,需要先从目标 API 的官方文档确认两件事:base_url 的具体值(是否含 /v1)以及请求路径。如果文档没有明说请求路径,那么 /chat/completions 可能不适用。

这个脚本的价值在于:它用最少的变量验证了“鉴权头 + 地址 + 请求体”的组合是否能通过,拿到 200 后再往下走。

逐项验证的清单

1. base_url

确认 base_url 是否与 OpenAI 完全相同。常见差异包括:

  • 是否带版本段(如 /v1
  • 是否带项目名称或自定义路径
  • 是否要求使用 https:// 前缀

如果你现有代码中把 base_url 写死了,迁移时至少要检查这一个值。

2. Authorization 头格式

开源生态中的 API 通常使用 Authorization: Bearer。但目标 API 是否也使用 Bearer 前缀、是否要求自定义前缀、是否区分大小写,都需要通过文档或实验确认。

还有一种情况:目标 API 可能要求额外的 header,比如 x-api-key 或自定义的用户标识。仅替换 key 变量不会覆盖这种差异。

3. API key 的格式与存储

不同服务的 key 可能:

  • 长度不同
  • 允许字符集不同
  • 有固定前缀标识
  • 支持多个子 key 或临时 key

不要把 key 硬编码在代码里。建议统一通过环境变量注入。这样切换服务时,不需要修改代码,只需要修改部署环境中的配置。

4. 错误响应结构

鉴权失败时,服务端返回的错误信息格式可能与 OpenAI 不同。比如:

  • 状态码是否复用(401/403/404)
  • 错误体是否为 JSON
  • 错误体中是否包含 error 字段
  • 字段名和嵌套结构是否一致

如果你现有代码中对错误响应有解析逻辑,比如读取 error.message,需要先确认目标 API 是否返回同样的结构。否则,即使鉴权成功,错误处理代码也可能在异常分支里崩溃。

5. 模型名称的可用性

迁移后,客户端发送的 model 字段名称是否需要在目标 API 中真实存在?如果目标 API 不识别该名称,可能在鉴权之后返回 400 或 404。

注意:模型名称不是鉴权的一部分,但却是迁移后最常见的“第二个失败点”。建议在验证清单中单独列出一条,用最小请求确认目标 API 对指定模型名的响应。

6. 流式响应的兼容性

如果业务依赖流式输出,需要验证目标 API 对 stream: true 的处理方式:

  • 响应是否使用 SSE(Server-Sent Events)
  • 数据帧格式是否相同
  • 结束标记是否存在,比如 [DONE]

流式解析代码对格式非常敏感。一个字符的差异,都可能导致客户端解析线程卡死。

7. 超时、重试与代理

网络层的问题同样会让迁移后的服务不稳定。

  • 超时:目标 API 的首字时间是否更长?如果旧配置 5 秒超时,新服务可能更容易触发超时
  • 重试:现有重试逻辑是依据状态码还是异常类型?目标 API 是否返回同样的状态码给可重试错误
  • 代理:你的运行环境是否设置了 HTTP_PROXY / HTTPS_PROXY?代理是否会拦截或改写 Authorization 头

很多本地调试失败的真实原因是代理。开发机上有代理时,requests 会默认走代理,而内部代理不一定允许访问外部 API 域名。这时需要检查环境变量,或者给请求显式传入 proxies 参数。

代码层面的最小改动模式

如果你的存量代码已经基于 OpenAI API 编写,迁移时建议做一次“配置隔离”,而不是在业务代码里散落修改。

用一个配置模块管理以下变量:

# config.py
import os

API_BASE_URL = os.environ.get("TARGET_BASE_URL")
API_KEY = os.environ.get("TARGET_API_KEY")
MODEL_NAME = os.environ.get("TARGET_MODEL_NAME", "test-model")
TIMEOUT_SECONDS = int(os.environ.get("TARGET_TIMEOUT_SECONDS", "30"))

业务代码只引用这些配置,不直接读环境变量。这样迁移时,只需改变环境变量即可,代码 diff 控制在最小范围。

更进一步,可以做一个轻量级 HTTP 客户端封装,在请求构造处集中设置 Authorization 头。如果目标 API 的鉴权头格式与 OpenAI 不同,只需要改这个封装函数,业务逻辑完全不受影响。

class ApiClient:
    def __init__(self, base_url: str, api_key: str, timeout: int = 30):
        self.base_url = base_url
        self.session = requests.Session()
        self.session.headers.update({"Authorization": f"Bearer {api_key}"})
        self.timeout = timeout

    def post(self, path: str, payload: dict):
        resp = self.session.post(
            f"{self.base_url}{path}",
            json=payload,
            timeout=self.timeout,
        )
        return resp

这段代码假设鉴权方式为 Bearer Token。如果验证后发现目标 API 使用不同的鉴权方式,只需要修改这个类的 __init__ 方法。

迁移风险与应对

迁移过程中最大的风险,是把“返回值恰好正确”误判为“完全兼容”。一次请求成功,不代表所有路径都成功。建议在正式迁移前,用真实业务流量(或回放)跑一遍对比测试,至少覆盖:

  • 正常请求
  • 鉴权失败
  • 模型不存在
  • 请求体过大
  • 流式中断
  • 网络超时

如果条件允许,可以搭建一个代理层,在迁移期间同时转发到旧、新两个 API,逐笔对比响应。这种方式能在不打断线上服务的情况下,发现隐藏差异。

结论

从 OpenAI API 迁到任何声称兼容的服务(如 DeepSeek)时,鉴权的价值是“验证”而不是“假设”。本文给出的验证清单,覆盖了 base_url、Authorization 头、API key、错误结构、模型名、流式响应和网络层。按顺序走一遍,能减少大部分“上线后才出错”的情况。

在动手改代码之前,先确认官方文档。如果文档没有写明某个细节,就用最小请求实测。所有结论都要建立在真实环境的响应之上,而不是另一个项目的经验。