HuanCode Docs

base_url 到底要不要带 /v1?源码级解析

用 OpenAI SDK 对接第三方 API 时,base_url 带不带 /v1 都能跑通?深入 Python 和 Node.js SDK 源码,彻底搞清楚 base_url 的拼接机制。

起因:一个让人困惑的配置项

用 OpenAI SDK 对接第三方 API(比如国内各种代理、中转服务)时,你一定见过这两种写法:

# 写法 A:带 /v1
client = OpenAI(
    api_key="sk-xxx",
    base_url="https://api.huancode.com/v1"
)

# 写法 B:不带 /v1
client = OpenAI(
    api_key="sk-xxx",
    base_url="https://api.huancode.com"
)

有些文档告诉你必须带 /v1,有些示例里又没带。更让人困惑的是——实测发现两种写法都能正常工作

到底应该带还是不带?为什么两种都行?要回答这个问题,得从源码入手。

SDK 怎么拼 URL

第一步:看 SDK 怎么用 base_url

Python SDK(openai v2.x)

打开 openai/_client.py,找到 OpenAI 类的构造函数:

# openai/_client.py
def __init__(self, *, base_url=None, ...):
    if base_url is None:
        base_url = os.environ.get("OPENAI_BASE_URL")
    if base_url is None:
        base_url = f"https://api.openai.com/v1"  # 👈 默认值自带 /v1

    super().__init__(base_url=base_url, ...)

关键发现 1:SDK 的默认值是 https://api.openai.com/v1,自带 /v1。如果你不传 base_url,它就用这个。

再往父类 _base_client.py 里追:

# openai/_base_client.py
def __init__(self, *, base_url, ...):
    self._base_url = self._enforce_trailing_slash(URL(base_url))
    # "https://api.openai.com/v1" → "https://api.openai.com/v1/"

关键发现 2:SDK 会给 base_url 自动补上末尾的 /

最核心的部分是 _prepare_url 方法——它决定了最终请求发到哪里:

# openai/_base_client.py
def _prepare_url(self, url: str) -> URL:
    """
    Merge a URL argument together with any 'base_url' on the client,
    to create the URL used for the outgoing request.
    """
    merge_url = URL(url)
    if merge_url.is_relative_url:
        merge_raw_path = self.base_url.raw_path + merge_url.raw_path.lstrip(b"/")
        return self.base_url.copy_with(raw_path=merge_raw_path)
    return merge_url

翻译成人话就是:base_url 的路径和接口路径直接拼接起来

那接口路径是什么?看 resources/chat/completions/completions.py

# openai/resources/chat/completions/completions.py
"/chat/completions",  # 👈 这就是接口路径,不带 /v1 前缀

拼接结果

base_url接口路径最终请求 URL
https://api.huancode.com/v1/chat/completionshttps://api.huancode.com/v1/chat/completions
https://api.huancode.com/chat/completionshttps://api.huancode.com/chat/completions

看到了吗?SDK 不会自动给你加 /v1,你传什么 base_url,它就在后面拼接口路径。

Node.js SDK(openai v6.x)

Node.js 版本逻辑完全一致:

// openai/client.js
constructor({ baseURL, ... } = {}) {
    // ...
    baseURL: baseURL || `https://api.openai.com/v1`,  // 默认值也带 /v1
}

URL 拼接逻辑:

// openai/client.js
const url = new URL(baseURL + (baseURL.endsWith('/') && path.startsWith('/')
    ? path.slice(1)
    : path));

同样是字符串直接拼接,不会额外注入 /v1

小结

OpenAI 官方 SDK 里,base_url 的作用非常单纯:

base_url + 接口路径 = 最终请求 URL

SDK 内部定义的接口路径是 /chat/completions/models 这样不带版本前缀的形式。版本前缀 /v1 是作为 base_url 的一部分传进来的。

第二步:那为什么不带也能用?

回到开头的问题——如果 SDK 不自动加 /v1,不带的话请求就打到了 /chat/completions 而不是 /v1/chat/completions,为什么也能通?

带 /v1 vs 不带 /v1

答案不在 SDK 里,而在服务端

以 LiteLLM(一个流行的 LLM API 代理)为例,查看它的 OpenAPI 规范:

{
  "/chat/completions": {
    "post": {
      "operationId": "chat_completion_chat_completions_post"
    }
  },
  "/v1/chat/completions": {
    "post": {
      "operationId": "chat_completion_v1_chat_completions_post"
    }
  }
}

LiteLLM 同时注册了两条路由/chat/completions/v1/chat/completions,它们指向同一个处理函数。所以不管你的 base_url 带不带 /v1,请求都能被正确处理。

类似的,很多第三方代理服务(如 one-api、new-api 等)也采用了这种兼容设计。

第三步:OpenAI 官方 API 呢?

回到 OpenAI 官方 API。官方的路由结构是:

https://api.openai.com/v1/chat/completions   ✅ 正常
https://api.openai.com/chat/completions       ❌ 404

OpenAI 官方只注册了 /v1/ 前缀的路由,所以对接官方 API 时,base_url 必须带 /v1。这也是为什么 SDK 默认值是 https://api.openai.com/v1

总结

场景base_url 建议原因
OpenAI 官方https://api.openai.com/v1官方只有 /v1/ 路由
LiteLLM 代理带不带都行服务端同时注册了两种路由
其他第三方代理建议带 /v1兼容性最好,即使服务端支持两种,带上也不会出错

最佳实践:始终带 /v1

最佳实践:始终在 base_url 中带上 /v1

理由很简单:

  1. 兼容 OpenAI 官方——切换回官方 API 时不用改代码
  2. 语义明确——/v1 表示 API 版本,是 URL 契约的一部分
  3. 不会出错——支持两种路由的服务端,带 /v1 一样能用
# ✅ 推荐写法
client = OpenAI(
    api_key="sk-xxx",
    base_url="https://your-proxy.com/v1"  # 始终带 /v1
)

下次再遇到这个问题,你可以自信地说:带上 /v1,准没错。

On this page