一句话定位

把上游 LLM API 接一层薄薄的透明代理,跑在 Docker 里,方便自己就近用,也方便统一管理 key 和放日志。

这个服务我在 MiniMax 的 Anthropic 兼容接口前挡了一层。两年以前,我自己为了连接 Claude,做了很重的适配中间件;
其实大多数场景用不到那么重——一个 FastAPI + httpx + Docker 就够。记录下来供以后复用。

干嘛用的

不是要做一个「产品」,是一个给自己用的透明转发层。典型场景:

  • Claude Code 连不上官方接口(网络原因)
  • 想把 API Key 统一放在内网服务器上,不散到每台开发机
  • 在不改 API 的前提下,中间插一层来记录请求延迟 / 用量
  • 统一对外的 base_url,切模型厂商时不用改客户端代码

架构

Claude Code / curl
        |
   Nginx(你的域名 / SSL 终结)
        |
   FastAPI(Docker, 内网 :5066)
        |
   api.minimaxi.com/anthropic(上游)

Nginx 管 SSL + 反向代理,FastAPI 只做透传。加了 Nginx 并不是必须的,
但有了它之后切域名、配证书都方便,不用改 Docker 容器。

两个文件搞定

app.py

from fastapi import FastAPI, Request
from fastapi.responses import StreamingResponse, PlainTextResponse
import httpx

app = FastAPI()

UPSTREAM = "https://api.minimaxi.com/anthropic"


@app.get("/")
async def index():
    return PlainTextResponse("test")


@app.api_route("/v1/{path:path}", methods=["POST", "GET", "OPTIONS"])
async def proxy(path: str, request: Request):
    body = await request.body()
    headers = dict(request.headers)
    headers.pop("host", None)
    headers.pop("content-length", None)
    headers.pop("connection", None)

    url = f"{UPSTREAM}/v1/{path}"

    async with httpx.AsyncClient(timeout=None) as client:
        resp = await client.request(
            method=request.method,
            url=url, content=body,
            headers=headers, params=request.query_params
        )
        if resp.status_code >= 400:
            return PlainTextResponse(
                resp.text, status_code=resp.status_code
            )
        return StreamingResponse(
            resp.aiter_bytes(),
            media_type=resp.headers.get("content-type", "")
        )

Dockerfile

FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY app.py .
EXPOSE 5066
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "5066"]

依赖只有三个:fastapi, uvicorn, httpx

构建和跑

docker build -t mini-proxy .
docker run -d --name mini-proxy -p 5066:5066 --restart always mini-proxy

Nginx 那边加一个 location 块:

location / {
    proxy_pass http://127.0.0.1:5066;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_buffering off;          # SSE 必须关
    chunked_transfer_encoding on;
}

几个注意的点

SSE 流式必须关 proxy_buffering

这是最常见的坑。Nginx 默认会 buffer 上游响应,SSE 就变成等整个流结束才发出去——那不叫流式了。
关掉之后 chunk 到即转发。

错误直接透传

不包装错误。上游返回什么就是什么,本地不加额外的 JSON 壳。
这样客户端收到的错误格式跟直连一样,不用做二次适配。

端口选 5066

没什么特殊理由,只是跟内网其他服务不冲突。

日志设计

如果要加日志,建议只记录结构化的元信息:

字段 示例
path /v1/messages
method POST
status 200
latency_ms 320
user_agent claude-code/1.0

不要记完整 body 和 SSE 内容。即使用内网,token 流里可能夹着敏感对话。记路径 + 耗时就够了。

总结

一个透传代理,走了三次迭代才稳定下来:第一次写的时候加了鉴权、限流、用量统计,太重了,
后面拆掉了,反而跑得更稳。

中转层越薄越好,薄到可以忘了它存在。