OpenAI API 直连 vs 中转:实测对比与接入教程

Category: Technical ExchangePublished:建议阅读时长:17分钟
Author: sodope llm

摘要

把模型调用直接写进业务代码,最初很快,但随着模型增加,地址、密钥、模型判断和错误处理会散落在多个文件里。本文用 FastAPI 封装一个最小的统一调用服务:从环境变量读取配置,通过模型白名单限制可调用模型,设置请求超时,并把常见错误转换成统一 JSON。示例使用 OpenAI 兼容协议,实际服务地址和模型名以所选平台文档为准。

一、最终要构建什么

这个小服务提供一个 POST /chat 接口。调用方只提交模型名和消息,服务端负责创建客户端、检查模型、发送请求并返回结果。项目结构如下:

model-gateway/
├── app.py
├── requirements.txt
├── .env.example
└── .gitignore

调用方不需要知道具体 SDK 客户端如何创建,也不应该把服务端密钥放进浏览器或移动端代码。后续增加模型时,只修改服务端允许列表和配置,不改变调用方协议。

二、准备环境

创建目录并安装依赖:

mkdir model-gateway
cd model-gateway
python -m venv .venv

Windows 激活虚拟环境:

.venv\\Scripts\\activate

Linux 或 macOS 激活虚拟环境:

source .venv/bin/activate

requirements.txt:

fastapi
uvicorn[standard]
OpenAI
python-dotenv

安装:

pip install -r requirements.txt

三、用环境变量保存配置

以下示例需要一个支持 OpenAI 兼容协议的服务地址,例如 jiekou.vip;实际地址、密钥和模型名以平台文档为准。

.env.example:

MODEL_API_KEY=sk_replace_me
MODEL_BASE_URL=https://example.invalid/v1
MODEL_TIMEOUT_SECONDS=30

不要把真实密钥提交到 Git。 .gitignore 至少加入:

.env
.venv/
__pycache__/

四、实现模型白名单和请求接口

app.py:

import os
from typing import Literal
from dotenv import load_dotenv
from fastapi import FastAPI, HTTPException
from openai import APIConnectionError, APIStatusError, APITimeoutError, AsyncOpenAI
from pydantic import BaseModel
load_dotenv()
MODEL_API_KEY = os.environ["MODEL_API_KEY"]
MODEL_BASE_URL = os.environ["MODEL_BASE_URL"]
MODEL_TIMEOUT = float(os.getenv("MODEL_TIMEOUT_SECONDS", "30"))
ALLOWED_MODELS = {
"model-a",
"model-b",
}
client = AsyncOpenAI(
api_key=MODEL_API_KEY,
base_url=MODEL_BASE_URL,
timeout=MODEL_TIMEOUT,
)
app = FastAPI(title="Model Gateway")
class ChatRequest(BaseModel):
model: Literal["model-a", "model-b"]
messages: list[dict[str, str]]
@app.post("/chat")
async def chat(request: ChatRequest):
if request.model not in ALLOWED_MODELS:
raise HTTPException(status_code=400, detail="model is not allowed")
try:
response = await client.chat.completions.create(
model=request.model,
messages=request.messages,
)
except APITimeoutError:
raise HTTPException(status_code=504, detail="upstream timeout")
except APIConnectionError:
raise HTTPException(status_code=502, detail="upstream connection failed")
except APIStatusError as exc:
status = exc.status_code if 400 <= exc.status_code < 600 else 502
raise HTTPException(status_code=status, detail="upstream request failed")
return {
"model": request.model,
"text": response.choices[0].message.content or "",
"usage": response.usage.model_dump() if response.usage else None,
}

这里有三个重要约束。第一,Literal 和 ALLOWED_MODELS 共同限制模型范围,调用方不能任意传入服务端支持的其他模型。第二,客户端使用异步接口,避免同步等待占满 Web 服务线程。第三,异常响应只返回通用错误描述,不把密钥、完整上游响应或内部堆栈直接返回给调用方。

五、启动并测试

启动服务:

uvicorn app:app --reload --port 8000

用 curl 发起请求:

curl -X POST http://127.0.0.1:8000/chat \
-H "Content-Type: application/json" \
-d '{"model":"model-a","messages":[{"role":"user","content":"只返回 OK"}]}'

成功响应的结构类似:

{
"model": "model-a",
"text": "OK",
"usage": {
"prompt_tokens": 12,
"completion_tokens": 1,
"total_tokens": 13
}
}

如果使用 Windows PowerShell,可以写成单行命令,避免不同 Shell 对换行符和引号的处理差异:

curl.exe -X POST http://127.0.0.1:8000/chat -H "Content-Type: application/json" -d "{\"model\":\"model-a\",\"messages\":[{\"role\":\"user\",\"content\":\"只返回 OK\"}]}"

六、常见错误怎么处理

400

请求参数或模型不在白名单

JSON 结构、模型名称和允许列表

401

鉴权失败

环境变量中的密钥和服务端权限

404

地址或模型路径不正确

MODEL_BASE_URL 是否重复拼接版本路径

429

上游限流

请求频率、并发数和重试策略

502

上游连接失败

服务地址、网络和 DNS

504

上游超时

超时设置、请求复杂度和上游状态

不要把所有错误都简单重试。400 和 401 通常需要修改配置;429 可以按服务端约定退避;502 和 504 是否重试,要结合请求是否幂等以及业务能否接受重复执行来决定。

七、为生产环境补齐的检查项

密钥管理。 使用部署平台的 Secret 或专用密钥服务,不把 .env 和真实密钥提交到仓库;不同应用使用不同密钥,便于撤销和定位。

请求边界。 限制消息长度、请求体大小和单次超时,避免一条请求长期占用资源。

模型策略。 模型白名单和默认模型放在配置中,模型切换要有版本记录,不要允许用户直接传入任意上游地址。

日志脱敏。 记录请求 ID、模型、耗时、状态码和用量摘要,不记录完整密钥,也不要默认记录用户原始提示词。

重试与降级。 只对明确的瞬时错误重试,并设置最大次数和退避时间。需要备用模型时,应记录降级原因,避免错误被静默吞掉。

监控和测试。 为成功、鉴权失败、模型不存在、限流、超时和上游连接失败分别写测试;上线前用一个低风险模型验证完整链路。

八、可以怎样继续扩展

这个版本只处理普通非流式响应。下一步可以增加流式输出、请求 ID、结构化日志和备用模型,但每项扩展都要先明确返回协议和失败语义。若接入工具调用或多模态输入,还要为消息类型和响应字段增加独立的数据模型,不能继续假设所有请求都是简单文本。

小结

把模型调用封装成一个小型 FastAPI 服务,重点不在于增加一层代码,而在于把配置、模型范围、超时和错误处理集中起来。先用环境变量和白名单建立边界,再用统一 JSON 和 curl 验证成功、失败和超时路径,最后补齐密钥、日志、重试和监控,才能从“能调用”走向“可维护”。

Share:
Contact Us