调用 DeepSeek API 时持续收到 401,通常不是密钥本身失效,而是鉴权信息在传递链路的某个环节被丢失或改写。本文从工程配置层面梳理排查顺序,帮助定位问题。

一、先确认密钥是否真正加载

最常见的情况是代码读取了环境变量,但变量并未注入进程。排查时不要只看 .env 文件是否存在,而要在运行时打印变量长度和前后几位(不要打印完整密钥):

import os
key = os.getenv("DEEPSEEK_API_KEY")
print("loaded:", bool(key), "len:", len(key) if key else 0)
print("prefix:", key[:6] if key else None)

如果 loaded 为 False,检查:

  • 启动命令是否在正确的 shell 或容器环境中执行;
  • .env 是否被实际加载(许多框架不会自动读取);
  • 容器编排中环境变量是否注入到应用容器而非构建阶段;
  • 变量名是否拼写一致,大小写敏感。

二、密钥格式校验

拿到密钥后先做基本格式检查。DeepSeek API Key 通常以固定前缀开头,若前缀明显不符,说明取到了错误的值(例如取到了其他服务的 key,或复制时带入了引号、空格、换行)。

key = key.strip().strip('"').strip("'")
assert key.startswith("sk-"), "key prefix mismatch"

注意:不要用 assert 在生产代码中做校验,这里仅用于本地排查。

三、Authorization 请求头拼装

DeepSeek API 使用 Bearer 鉴权,请求头格式为:

Authorization: Bearer 

手动构造请求时容易犯两类错误:

  1. 漏掉 Bearer 前缀,只写了密钥;
  2. 前缀与密钥之间缺少空格,或多了空格。
headers = {
    "Authorization": f"Bearer {key}",
    "Content-Type": "application/json",
}

如果使用 requests,注意不要同时传入 auth= 参数和 Authorization 头,两者可能冲突导致最终头被覆盖。

四、代理与网关中的头覆盖

在服务端部署时,请求往往经过 Nginx、API 网关或公司内网代理。这些中间层可能:

  • 丢弃 Authorization 头;
  • 重写为其他鉴权头;
  • 在转发时追加自己的鉴权信息,覆盖原始值。

排查方法是在代理层打印最终转发出去的头(注意脱敏),或临时绕过代理直连验证。如果直连正常、经代理失败,问题基本在中间层。

五、SDK 默认配置与手动 HTTP 调用的差异

使用官方 SDK 时,鉴权头由 SDK 自动注入,通常只需在初始化时传入 api_key。手动 HTTP 调用则需要自己拼装。两者行为不一致时,重点检查:

  • SDK 初始化时传入的 key 是否为空或为占位符;
  • SDK 是否读取了与手动调用不同的环境变量名;
  • SDK 版本是否过旧,导致请求路径或头格式与当前 API 不匹配。

建议先用最小手动 HTTP 请求验证密钥有效性,再回到 SDK 排查配置。

import requests

resp = requests.post(
    "https://api.deepseek.com/chat/completions",
    headers={"Authorization": f"Bearer {key}"},
    json={
        "model": "deepseek-chat",
        "messages": [{"role": "user", "content": "ping"}],
    },
    timeout=30,
)
print(resp.status_code, resp.text[:200])

六、可复用的鉴权配置检查清单

  1. 运行时确认环境变量已加载,长度与预期一致;
  2. 去除密钥首尾空白、引号、换行;
  3. 确认前缀符合预期;
  4. 请求头为 Authorization: Bearer,空格正确;
  5. 未同时使用 auth= 与手动 Authorization 头;
  6. 代理/网关未丢弃或覆盖鉴权头;
  7. SDK 初始化参数与环境变量名一致;
  8. 先用最小手动请求验证,再排查 SDK;
  9. 401 响应体中的错误信息可用于区分“密钥无效”与“头缺失”;
  10. 更换密钥后重启进程,避免旧值缓存。

七、边界与失败模式

  • 密钥轮换后未重启服务,进程仍持有旧密钥;
  • 多环境共用同一变量名,测试环境覆盖了生产值;
  • 日志中打印了完整密钥,导致泄露后被动失效;
  • 代理层对 Authorization 做了大小写归一化,某些实现可能不兼容。

按上述顺序排查,多数持续 401 都能定位到具体的配置环节。