推理客户端在发起请求前,通常会根据 Base URL 和固定的资源路径拼出完整地址。以 OpenAI 兼容接口为例,一次对话补全的最终请求地址一般由 Base URL 加上 /chat/completions 组成。Base URL 设置成 http://localhost:11434/v1 时,客户端会继续在后面追加资源;如果设置成 http://localhost:11434/v1/chat/completions,客户端再追加就会得到错误地址。因此 Base URL 通常只是协议、主机、端口和基础路径的组合,而不是完整接口地址。

理解这一点对自定义推理服务尤为重要。很多本地推理程序并不会把 OpenAI 兼容层放在站点根路径,而是统一挂在 /v1 下。这个 /v1 不只是版本标识,也承担了路由隔离。客户端库不会关心服务端内部如何实现,只会机械地拼接。把 Base URL 配成不含 /v1 的根地址,请求就会打到错误路由,常见的表现就是 404 或模型列表为空。
一、Base URL 的解析规则与拼接差异
不同客户端库对 Base URL 的解析并非完全一致。以 Python 的 OpenAI SDK 为例,它会在 base_url 后追加 /chat/completions、/models 等资源路径。如果传入的 Base URL 末尾含有斜杠,多数客户端会先去除末尾斜杠再拼接,但也有少数绑定层直接做字符串相加。为了避免这类差异带来的不确定性,自定义服务端点应统一写成不带末尾斜杠的形式,例如 http://localhost:11434/v1。
更准确地说,Base URL 里不应该包含最终资源名,但必须包含服务暴露的基础前缀。比如 Ollama 的 OpenAI 兼容层实际挂在 /v1 路径下,因此 Base URL 就应包含 /v1;而某些老版本服务可能把接口直接暴露在根路径,此时 Base URL 只需要主机和端口。如果不清楚服务是否带版本前缀,可以通过请求 /v1/models 和 /models 快速比对响应。
# 伪代码:客户端拼接完整地址
base_url = "http://localhost:11434/v1"
resource = "/chat/completions"
full_url = base_url.rstrip("/") + resource
print(full_url)
# 输出:http://localhost:11434/v1/chat/completions
上面的逻辑代表多数客户端的处理方式。真正发送请求时还会经过连接池、重定向和代理设置,但核心路径拼接仍遵循这个规则。只要记住 Base URL 指向的是接口前缀而不是完整接口,配置时就能减少很多无谓的排查。
二、主流推理服务的默认 Base URL
目前主流的本地推理服务大多提供 OpenAI 兼容接口,但默认端口和基础路径并不相同。下面列出一部分常见服务的默认访问地址,方便统一接入时参考。
| 服务 | 默认 Base URL | 说明 |
|---|---|---|
| Ollama | http://localhost:11434/v1 | OpenAI 兼容层默认启用 |
| LM Studio | http://localhost:1234/v1 | 本地服务,支持 OpenAI 接口 |
| vLLM | http://localhost:8000/v1 | 启动时需指定模型 |
| LocalAI | http://localhost:8080/v1 | 可运行在 CPU 环境 |
| text-generation-webui | http://127.0.0.1:5000/v1 | 需开启 API 模式 |
这些地址都是本机回环地址,适合开发调试。如果服务部署在远程服务器或容器中,需要把主机部分替换为实际 IP 或域名,并使用 http:// 或 https:// 正确标记协议。尤其是容器网络,localhost 在宿主机访问容器时会失效,应当使用映射端口和宿主机地址。
有一类推理平台会提供云端 Base URL,例如 OpenRouter、DeepInfra 等,它们的地址通常同样以 /v1 结尾,但需要额外传入 API Key。无论哪种服务,接入前最好用 /v1/models 这类轻量接口确认地址可用。
三、在 OpenAI SDK 中配置自定义 Base URL
OpenAI 官方 SDK 支持通过 base_url 参数把请求指向任何兼容服务。下面以本地 Ollama 为例,展示 Python 调用方式。
from openai import OpenAI
client = OpenAI(
base_url="http://localhost:11434/v1",
api_key="ollama", # 本地服务可传任意非空字符串
)
response = client.chat.completions.create(
model="llama3",
messages=[
{"role": "user", "content": "你好"}
],
)
print(response.choices[0].message.content)
这里 api_key 虽然对 Ollama 没有实际鉴权意义,但 SDK 会做非空校验,传一个占位值即可。对于需要鉴权的服务,则要把 api_key 换成真实密钥,并保证密钥不会泄露到客户端。
很多 SDK 也支持环境变量读取配置。Python 的 OpenAI 客户端在没有显式传参时,会读取 OPENAI_API_KEY 和 OPENAI_BASE_URL。因此可以把自定义地址写入环境变量,避免代码中硬编码。
export OPENAI_BASE_URL=http://localhost:11434/v1 export OPENAI_API_KEY=ollama
如果客户端不是 Python,也可以使用同样的参数名或配置文件机制,但要注意不同语言的 SDK 对环境变量的支持程度不同。比如某些社区维护的客户端只认 baseURL 这种驼峰写法,接入时应查看对应文档。
四、通过反向代理统一多个推理后端
当同一台机器或内网中存在多个推理服务时,给每个客户端分别配置不同地址会很麻烦。更优雅的做法是在前面加一层 OpenAI 兼容网关或反向代理,客户端只保存一个 Base URL,由网关根据模型名把请求分发到对应后端。
如果只是想给单个本地服务增加统一入口或请求头,可以用 Nginx 做简单反向代理。下面配置把本机 8001 端口转发到 Ollama 的 11434 端口,并保留 /v1 路径。
server {
listen 8001;
server_name localhost;
location /v1/ {
proxy_pass http://127.0.0.1:11434/v1/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
这里的 proxy_pass 末尾带有斜杠,Nginx 会把匹配到的 /v1/ 前缀替换为目标地址中的 /v1/,其余路径原样转发。如果末尾不加斜杠,路径拼接行为会不同,容易出现资源路径重复或丢失。反向代理适合单后端场景,但如果需要按模型动态路由到不同服务,仅靠 Nginx 的静态 location 配置会受限。
聚合网关方案更灵活。LiteLLM、One API 等项目本身实现了 OpenAI 接口,可以在统一端口下挂载多个上游模型。使用者只需要在网关配置中声明模型名和对应的上游地址,客户端 Base URL 指向网关即可。
model_list:
- model_name: llama3
litellm_params:
model: ollama/llama3
api_base: http://localhost:11434
- model_name: qwen2
litellm_params:
model: vllm/qwen2
api_base: http://localhost:8000
这样客户端调用 llama3 时,网关自动请求 Ollama;调用 qwen2 时,网关自动请求 vLLM。对使用方来说,Base URL 始终是网关地址,例如 http://localhost:4000/v1,极大降低了配置工作量。
五、常见配置陷阱与调试步骤
自定义 Base URL 最常见的问题集中在路径上。把地址写成 /v1/chat/completions 属于典型错误,因为客户端还会追加资源路径;漏写 /v1 则会导致请求落到根路由,很多服务直接返回 404。另一个细节是结尾斜杠,虽然多数客户端会做处理,但统一不带末尾斜杠可以减少不确定性。
远程部署时还要区分 http 和 https,以及证书是否有效。自签名证书环境下,客户端默认会拒绝连接,需要在初始化时关闭证书校验或配置本地证书。比如 Python 客户端可以在 OpenAI 构造参数中传入 http_client,但更简单的方式是在测试环境使用 base_url 的 http:// 地址,并确认防火墙允许访问。
调试时先绕过 SDK,用 curl 直接请求基础接口。比如执行 curl http://localhost:11434/v1/models,如果返回模型列表,说明服务本身和路径都正常;如果返回 404,就可以把问题定位到路径前缀。然后可以开启客户端的请求日志,检查实际拼接后的完整 URL。Python 的 OpenAI SDK 可以通过设置 httpx 日志级别或抓包工具查看请求。很多时候,真正的 Base URL 只差一个 /v1 或一个端口号。