技术教程 · 阅读约 11 分钟

自建 AI 网关:一套代码同时接入 GPT-4o / Claude / Gemini,账号被封也不慌

自建 AI 网关:一套代码同时接入 GPT-4o / Claude / Gemini,账号被封也不慌

最近 V2EX 上有人贴出 OpenAI 账号被封的帖子,底下一片「我也是」。更麻烦的是:你的项目代码、公司的内部工具,全硬编码了 api.openai.com。封号那一刻,不是换个 key 的事,是整套服务直接瘫痪。

反直觉的结论:你真正需要的不是更稳定的 OpenAI 账号,而是一个不依赖任何单一服务商的调用层。

这篇文章教你用 100 行 Python 搭一个本地 AI 网关路由器,实现:

  • 按模型名自动分发到不同后端
  • 任意一家挂了自动 fallback
  • 统一 OpenAI 格式,上层代码零改动

为什么要自己搞网关

先说问题根源。大多数人的 AI 调用栈长这样:


业务代码 → openai.ChatCompletion.create(model="gpt-4o") → api.openai.com

一旦账号被封、访问受限、或者你想换 Claude 试试效果,就得全局搜代码改 endpoint。项目越大,改得越痛苦。

理想的结构应该是:


业务代码 → 本地网关 → 按策略路由 → GPT / Claude / Gemini / DeepSeek

上层只认一个地址,后端随便换。


动手:用 FastAPI 搭最小可用网关

需要:Python 3.10+,安装依赖:


pip install fastapi uvicorn httpx python-dotenv

新建 gateway.py:


import os

import httpx

from fastapi import FastAPI, Request, HTTPException

from fastapi.responses import StreamingResponse

import json



app = FastAPI()



# ---- 路由表:按 model 前缀决定转发到哪个后端 ----

# 这里统一用无量Api,它聚合了 300+ 模型,一个 key 全搞定

BACKEND_URL = "https://api2everything.xyz/v1"

API_KEY = os.getenv("WULIANG_API_KEY", "your-key-here")



# 如果你有多个 key,可以在这里配置轮询或 fallback

FALLBACK_KEYS = [

    os.getenv("WULIANG_API_KEY_1", API_KEY),

    os.getenv("WULIANG_API_KEY_2", API_KEY),

]



def pick_key(attempt: int) -> str:

    return FALLBACK_KEYS[attempt % len(FALLBACK_KEYS)]



@app.post("/v1/chat/completions")

async def proxy_chat(request: Request):

    body = await request.json()

    model = body.get("model", "gpt-4o")



    # 可选:在这里做模型别名映射

    model_alias = {

        "fast":   "gpt-4o-mini",

        "smart":  "claude-opus-4-5",

        "cheap":  "deepseek-chat",

        "vision": "gemini-2.0-flash",

    }

    body["model"] = model_alias.get(model, model)



    headers = {

        "Content-Type": "application/json",

    }



    is_stream = body.get("stream", False)

    last_error = None



    for attempt in range(len(FALLBACK_KEYS)):

        headers["Authorization"] = f"Bearer {pick_key(attempt)}"

        try:

            if is_stream:

                return await _stream_request(body, headers)

            else:

                return await _normal_request(body, headers)

        except Exception as e:

            last_error = e

            continue  # 自动 fallback 到下一个 key



    raise HTTPException(status_code=502, detail=f"All backends failed: {last_error}")





async def _normal_request(body: dict, headers: dict):

    async with httpx.AsyncClient(timeout=60) as client:

        resp = await client.post(

            f"{BACKEND_URL}/chat/completions",

            json=body,

            headers=headers,

        )

        resp.raise_for_status()

        return resp.json()





async def _stream_request(body: dict, headers: dict):

    async def generator():

        async with httpx.AsyncClient(timeout=120) as client:

            async with client.stream(

                "POST",

                f"{BACKEND_URL}/chat/completions",

                json=body,

                headers=headers,

            ) as resp:

                async for chunk in resp.aiter_bytes():

                    yield chunk



    return StreamingResponse(generator(), media_type="text/event-stream")





# 透传 /v1/models 接口,方便调试

@app.get("/v1/models")

async def list_models():

    headers = {"Authorization": f"Bearer {API_KEY}"}

    async with httpx.AsyncClient(timeout=10) as client:

        resp = await client.get(f"{BACKEND_URL}/models", headers=headers)

        return resp.json()

启动:


export WULIANG_API_KEY="sk-xxxxxxxx"

uvicorn gateway:app --host 0.0.0.0 --port 8080


接入验证

本地网关起来之后,你的业务代码一行都不用改逻辑,只改 base_url:


from openai import OpenAI



client = OpenAI(

    api_key="any-string",          # 网关自己管 key,这里随便填

    base_url="http://localhost:8080/v1",

)



# 用别名:自动路由到 deepseek-chat,最便宜

resp = client.chat.completions.create(

    model="cheap",

    messages=[{"role": "user", "content": "解释一下 Python GIL"}],

)

print(resp.choices[0].message.content)



# 用别名:自动路由到 claude-opus-4-5,适合复杂推理

resp = client.chat.completions.create(

    model="smart",

    messages=[{"role": "user", "content": "帮我做这段代码的架构 review"}],

    stream=True,

)

for chunk in resp:

    print(chunk.choices[0].delta.content or "", end="", flush=True)

用 curl 快速冒烟:


curl http://localhost:8080/v1/chat/completions \

  -H "Content-Type: application/json" \

  -d '{"model":"fast","messages":[{"role":"user","content":"hi"}]}'


进阶:加一层成本追踪

生产环境里你肯定想知道每天烧了多少钱。在 gateway.py 里加个中间件:


import time

from collections import defaultdict



cost_log = defaultdict(float)



@app.middleware("http")

async def log_usage(request: Request, call_next):

    start = time.time()

    response = await call_next(request)

    elapsed = time.time() - start



    if request.url.path == "/v1/chat/completions":

        # 粗略记录请求次数,精细化可解析 usage 字段

        cost_log["requests"] += 1

        cost_log["latency_total"] += elapsed



    return response



@app.get("/stats")

def stats():

    return dict(cost_log)


关于后端选哪家

上面代码里我用的是无量Api(api2everything.xyz),原因很直接:

1. 聚合了 300+ 模型,GPT-4o、Claude Opus、Gemini、DeepSeek 一个 key 全覆盖,不用管各家账号的死活

2. 价格比官方便宜约 65%,SVIP 再打 7.7 折——同样的预算能跑更多请求

3. 完全 OpenAI 格式兼容,把上面代码的 BACKEND_URL 换成它就行,没有任何适配成本

4. 注册送 ¥1 余额,余额永久不过期,拿来测试很合适

国内直连,延迟比自己挂代理打 OpenAI 稳定很多,这对生产服务来说不是小事。

注册地址:https://api2everything.xyz/?utm_source=v2ex&utm_medium=article&utm_campaign=daily&aff=wqUb


小结

账号被封这件事会反复发生,与其每次手忙脚乱,不如一次性把调用层抽出来。上面这个网关的核心逻辑不超过 100 行,部署在本地或者一台小 VPS 上,后端随时换,上层代码永远不动。

有问题欢迎评论区聊,模型路由策略、流量限速、多租户 key 管理这些进阶需求都可以展开写。觉得有用的话点个赞,下次继续。