【学习笔记】OpenAI 兼容 API 服务化:从协议到 LLM Gateway(19/35)

📅 2026/7/13 6:21:07 👁️ 阅读次数 📝 编程学习
【学习笔记】OpenAI 兼容 API 服务化:从协议到 LLM Gateway(19/35)

前面几篇我们反复提到OpenAI 兼容 API——vLLM、SGLang、Ollama、TGI、lmdeploy 全都支持它。

为什么这件事这么重要?

举个真实的工程场景:你的团队同时在用:

  • 闭源 API:Claude、GPT-5、Gemini、DeepSeek API

  • 自部署 vLLM:内网 Qwen3-32B

  • 自部署 SGLang:Agent 业务用

  • 本地 Ollama:开发同学的笔记本

  • 未来还要接:阿里通义、Moonshot、智谱

如果每家都用自己的 SDK,你的客户端代码要维护 N 套。但如果它们都"长得像" OpenAI API,你的客户端只用openai库就够了——只需要改base_urlmodel参数

这就是 OpenAI 兼容 API 的事实标准价值

这一篇我们要彻底搞清这个事:

  1. OpenAI API 协议的关键设计

  2. 各家推理框架的"兼容程度"和差异点

  3. 怎么自己实现一个 OpenAI 兼容代理

  4. 怎么设计 LLM Gateway 统一多模型 + 计费 + 限流 + 缓存

读完本文你将能:

  • 写出符合 OpenAI 协议的兼容 API 实现

  • 选对 LLM Gateway 工具(LiteLLM / Portkey / 自研)

  • 在生产环境跑一个多模型路由 + 容灾 + 计费 + 缓存的网关

我们开始。


一、OpenAI 兼容 API:怎么变成「事实标准」的

1.1 历史回顾

  • 2020.06:GPT-3 API 发布,OpenAI 定义了/v1/completions接口

  • 2023.03:ChatGPT API 上线,/v1/chat/completions接口诞生

  • 2023.06:vLLM 第一次提供「OpenAI 兼容 API」选项

  • 2023 下半年:所有主流推理框架跟进

  • 2024:Anthropic、Google 等"对手"也开始提供 OpenAI 兼容接口(兼容层)

  • 2026:OpenAI 兼容已是行业默认

为什么是 OpenAI 的协议被「奉为标准」?

  1. 先发优势:GPT-3 / ChatGPT 火得早,大家先按它对接

  2. 设计合理:协议简洁清晰,支持流式 / Tool Use / 多模态扩展

  3. 生态压力openaiPython SDK 太流行,所有客户端都用它

  4. 社区共识:大家都不想发明新协议

1.2 OpenAI 兼容 API 给工程师带来什么

好处

真实价值

客户端 SDK 通用

openai

库一统天下

切换模型零成本

base_url即可

工具生态共享

LangChain / LlamaIndex 等开箱即用

测试方便

curl / Postman 直接测

AB 测试简单

多个 URL 随便切


二、OpenAI API 协议详解

2.1 核心端点

OpenAI API 主要有这几个端点:

GET /v1/models ── 列出可用模型 POST /v1/chat/completions ── 对话补全(主力接口) POST /v1/completions ── 文本补全(旧版,少用) POST /v1/embeddings ── 文本向量化 POST /v1/audio/transcriptions ── 音频转文字 POST /v1/audio/speech ── 文字转语音 POST /v1/images/generations ── 图像生成 POST /v1/files ── 文件上传 POST /v1/batches ── 批处理(异步)

大模型推理服务最关键的是chat completions 和 embeddings

2.2 /v1/chat/completions 详解

完整请求体:

{ "model":"qwen3-32b", "messages":[ {"role":"system","content":"你是助手"}, {"role":"user","content":"解释 KV Cache"} ], "temperature":0.7, "top_p":0.9, "max_tokens":500, "n":1, "stream":false, "stop":["\n\n"], "presence_penalty":0, "frequency_penalty":0, "logit_bias":{}, "user":"user-id-123", "tools":[...], "tool_choice":"auto", "response_format":{"type":"json_object"}, "seed":42, "logprobs":false, "top_logprobs":null }

完整响应

{ "id":"chatcmpl-xxx", "object":"chat.completion", "created":1735000000, "model":"qwen3-32b", "choices":[{ "index":0, "message":{ "role":"assistant", "content":"KV Cache 是..." }, "finish_reason":"stop" }], "usage":{ "prompt_tokens":25, "completion_tokens":150, "total_tokens":175 } }

2.3 流式输出(SSE)

stream: true时返回 Server-Sent Events:

data: {"id":"chatcmpl-xxx","choices":[{"delta":{"role":"assistant","content":""},"index":0}]} data: {"id":"chatcmpl-xxx","choices":[{"delta":{"content":"KV"},"index":0}]} data: {"id":"chatcmpl-xxx","choices":[{"delta":{"content":" Cache"},"index":0}]} data: {"id":"chatcmpl-xxx","choices":[{"delta":{"content":" 是..."},"index":0}]} data: {"id":"chatcmpl-xxx","choices":[{"finish_reason":"stop","delta":{},"index":0}]} data: [DONE]

两个关键点

  1. 每行以data:开头,空行分隔

  2. 最后以data: [DONE]结束

Python 客户端处理:

from openai import OpenAI client = OpenAI(base_url="...", api_key="...") stream = client.chat.completions.create( model="qwen3-32b", messages=[{"role": "user", "content": "hi"}], stream=True, ) for chunk in stream: delta = chunk.choices[0].delta.content or "" print(delta, end="", flush=True)

2.4 Function Calling / Tool Use

让模型能调用外部工具,这是 Agent 的基石:

{ "model":"qwen3-32b", "messages":[{"role":"user","content":"上海今天天气怎么样?"}], "tools":[{ "type":"function", "function":{ "name":"get_weather", "description":"查询某个城市的天气", "parameters":{ "type":"object", "properties":{ "city":{"type":"string"} }, "required":["city"] } } }], "tool_choice":"auto" }

响应:

{ "choices":[{ "message":{ "role":"assistant", "tool_calls":[{ "id":"call_xxx", "type":"function", "function":{ "name":"get_weather", "arguments":"{\"city\":\"上海\"}" } }] }, "finish_reason":"tool_calls" }] }

第二轮,把工具调用结果传回去:

{ "messages":[ {"role":"user","content":"上海今天天气怎么样?"}, {"role":"assistant","tool_calls":[...]}, {"role":"tool","tool_call_id":"call_xxx","content":"晴,25°C"} ] }

👉 详见系列第 27 篇:Function Calling / Tool Use 实战

2.5 多模态(Vision)

OpenAI 在 Vision 接入上也定义了事实标准:

{ "model":"qwen-vl-max", "messages":[{ "role":"user", "content":[ {"type":"text","text":"这张图里有什么?"}, {"type":"image_url","image_url":{"url":"https://..."}} ] }] }

也支持 base64:

{"type": "image_url", "image_url": {"url": "data:image/png;base64,iVBORw..."}}

2.6 JSON Mode

强制 JSON 输出:

{ "model": "qwen3-32b", "messages": [{"role": "user", "content": "..."}], "response_format": {"type": "json_object"} }

更强的Structured Outputs(JSON Schema 严格约束):

{ "response_format":{ "type":"json_schema", "json_schema":{ "name":"person", "schema":{ "type":"object", "properties":{ "name":{"type":"string"}, "age":{"type":"number"} }, "required":["name","age"] }, "strict":true } } }

框架兼容性

  • vLLM:支持,靠 outlines / xgrammar 后端

  • SGLang:最强支持(regex 100% 保证)

  • Ollama:基本支持

  • TGI:部分支持


三、各家框架的「兼容程度」与差异

3.1 兼容性对比

特性

vLLM

SGLang

TGI

Ollama

lmdeploy

/v1/chat/completions

/v1/completions

/v1/embeddings

/v1/models

流式输出

Function Calling

Vision (多模态)

JSON Mode

✅ ⭐

Logprobs

seed

(可重复性)

3.2 框架扩展参数(注意非标准)

每家框架都加了"自己的扩展参数",用了就不兼容 OpenAI 标准了

vLLM 扩展

{ "guided_json":{...}, // JSON Schema 约束 "guided_regex":"^\\d{4}-\\d{2}$", // 正则约束 "guided_choice":["A","B","C"], // 选项约束 "use_beam_search":true, // beam search "best_of":4, "repetition_penalty":1.05, "lora_request":{"lora_name":"..."} // 多 LoRA 路由 }

SGLang 扩展

{ "regex": "...", "json_schema": {...}, "ignore_eos": true, "skip_special_tokens": false }

生产建议只用标准参数。需要扩展能力时,再做包装。这样切换框架成本低。

3.3 一个真实「兼容性陷阱」

很多人以为"OpenAI 兼容"就是 100% 兼容。事实上是 70-90% 兼容

我们曾遇到的坑:

  • vLLM 早期版本不支持seed参数 → 测试用例随机失败

  • TGI 的 streaming chunk 格式略有差异 → 客户端 SDK 解析失败

  • Ollama 的tool_calls字段早期被放错位置 → Agent 框架不识别

  • 某框架对temperature=0处理与 OpenAI 不一致 → 输出不稳定

对策

  1. openai官方 Python SDK 测试,能跑通才能上线

  2. 关键参数(temperature / max_tokens / stream)必须做端到端测试

  3. 灰度上线,监控成功率


四、自己实现一个 OpenAI 兼容代理

4.1 场景

很多团队会自己写一个OpenAI 兼容代理,原因:

  • 后端可能是 vLLM、SGLang、本地小模型混合

  • 需要统一鉴权、限流、计费、监控

  • 想做 fallback、缓存等高级特性

下面是一个生产可用的极简实现(基于 FastAPI)。

4.2 完整代码(约 200 行)

""" OpenAI 兼容代理 - 基础版 依赖:pip install fastapi uvicorn httpx """ import os import json import time import asyncio from typing importAny, AsyncGenerator from fastapi import FastAPI, Request, HTTPException, Header from fastapi.responses import StreamingResponse, JSONResponse import httpx app = FastAPI(title="LLM Proxy") # 模型 → 后端映射 ROUTING = { "qwen3-32b": {"url": "http://vllm-qwen:8000", "real_model": "Qwen/Qwen3-32B-Instruct"}, "qwen3-7b": {"url": "http://ollama:11434", "real_model": "qwen3:7b-instruct"}, "deepseek-r1": {"url": "http://sglang-r1:30000", "real_model": "deepseek-r1"}, "claude-fall": {"url": "https://api.anthropic.com", "real_model": "claude-sonnet-4-6", "type": "anthropic"}, } # 鉴权 VALID_KEYS = set(os.getenv("API_KEYS", "sk-test").split(",")) defauth(authorization: str | None): ifnot authorization ornot authorization.startswith("Bearer "): raise HTTPException(401, "Missing API key") key = authorization[7:] if key notin VALID_KEYS: raise HTTPException(403, "Invalid API key") return key @app.get("/v1/models") asyncdeflist_models(authorization: str = Header(None)): auth(authorization) return { "object": "list", "data": [ {"id": name, "object": "model", "owned_by": "internal"} for name in ROUTING ] } @app.post("/v1/chat/completions") asyncdefchat_completions(req: Request, authorization: str = Header(None)): user_key = auth(authorization) body = await req.json() model = body.get("model") if model notin ROUTING: raise HTTPException(404, f"Model '{model}' not found") route = ROUTING[model] backend_body = {**body, "model": route["real_model"]} stream = body.get("stream", False) # 记录请求 start_time = time.time() request_id = f"req-{int(start_time*1000)}" if stream: return StreamingResponse( forward_stream(route, backend_body, request_id, user_key), media_type="text/event-stream", headers={"X-Request-ID": request_id} ) else: returnawait forward_sync(route, backend_body, request_id, user_key) asyncdefforward_sync(route, body, req_id, user_key): """同步转发""" timeout = httpx.Timeout(300.0, connect=10.0) asyncwith httpx.AsyncClient(timeout=timeout) as client: try: resp = await client.post( f"{route['url']}/v1/chat/completions", json=body, headers={"Content-Type": "application/json"}, ) resp.raise_for_status() data = resp.json() # 记日志(计费、监控) log_usage(req_id, user_key, body["model"], data.get("usage", {})) return JSONResponse(content=data) except httpx.HTTPStatusError as e: raise HTTPException(e.response.status_code, e.response.text) except Exception as e: raise HTTPException(500, str(e)) asyncdefforward_stream(route, body, req_id, user_key) -> AsyncGenerator[bytes, None]: """流式转发""" timeout = httpx.Timeout(600.0, connect=10.0) total_tokens = 0 asyncwith httpx.AsyncClient(timeout=timeout) as client: asyncwith client.stream( "POST", f"{route['url']}/v1/chat/completions", json=body, headers={"Content-Type": "application/json"}, ) as resp: if resp.status_code != 200: err = await resp.aread() yieldf'data: {{"error": "{err.decode()}"}}\n\n'.encode() return asyncfor line in resp.aiter_lines(): ifnot line: continue yieldf"{line}\n\n".encode() # 统计 token(简化版,实际要解析 chunk) total_tokens += 1 log_usage(req_id, user_key, body["model"], {"completion_tokens": total_tokens}) deflog_usage(req_id, user, model, usage): """记录使用量(用于计费、监控)""" print(f"[USAGE] {req_id} user={user} model={model} usage={usage}") # 生产环境写 Redis / Kafka / 数据库 if __name__ == "__main__": import uvicorn uvicorn.run(app, host="0.0.0.0", port=8000)

4.3 启动 + 测试

# 启动 API_KEYS=sk-prod-1,sk-prod-2 python proxy.py # 测试 curl http://localhost:8000/v1/chat/completions \ -H "Authorization: Bearer sk-prod-1" \ -H "Content-Type: application/json" \ -d '{ "model": "qwen3-32b", "messages": [{"role":"user","content":"hi"}], "stream": true }'

这个代理已经具备

  • 模型路由

  • API key 鉴权

  • 流式 / 非流式转发

  • 使用量记录

  • 超时控制

后面我们扩展它加更多特性。


五、LLM Gateway:完整的生产架构

5.1 为什么要 Gateway

随着业务变复杂,简单代理不够用。需要一个完整的LLM Gateway

┌─────────────┐ 应用 1 (聊天) ──┐ │ │ ┌── vLLM (Qwen) 应用 2 (Agent) ─┼──→│ LLM Gateway │ ─┼── SGLang (DeepSeek) 应用 3 (RAG) ──┤ │ │ ├── Claude API 应用 4 (Code) ──┘ │ │ └── GPT-5 API └─────────────┘ ↑↑↑↑↑↑↑↑↑↑↑↑↑ 鉴权 / 限流 / 计费 缓存 / 监控 / 容灾 Fallback / 路由 / 灰度

Gateway 的核心职责

  1. 统一接口:所有应用看到的都是 OpenAI 标准

  2. 多后端路由:按模型名 / 优先级 / 业务路由

  3. 鉴权:API Key 管理 + 配额

  4. 限流:QPS / TPM 限制

  5. 计费:token 使用 → 成本核算

  6. 缓存:相同请求复用结果

  7. 监控:延迟、错误率、成本可视化

  8. 容灾:上游失败自动 fallback

  9. 审计:日志、合规审查

5.2 主流 Gateway 方案对比

方案

类型

优势

适合

LiteLLM

开源 Python

100+ 模型支持,社区活跃

首选

Portkey

商业 SaaS

监控强、可视化好

企业

Kong AI Gateway

商业 / 开源

与 Kong API Gateway 集成

已用 Kong

Cloudflare AI Gateway

云服务

全球节点 / 边缘缓存

全球应用

自研

自己写

完全可控

大厂

5.3 LiteLLM 实战

LiteLLM 是 2024 年开源社区最火的 Gateway,它把 100+ 模型 API 都映射到 OpenAI 协议

部署 LiteLLM Proxy

pip install litellm[proxy] # 创建配置文件 config.yaml cat > config.yaml << 'EOF' model_list: - model_name: qwen3-32b litellm_params: model: openai/Qwen/Qwen3-32B-Instruct api_base: http://vllm-qwen:8000/v1 api_key: sk-dummy - model_name: claude litellm_params: model: anthropic/claude-sonnet-4-6 api_key: os.environ/ANTHROPIC_API_KEY - model_name: gpt-5 litellm_params: model: openai/gpt-5 api_key: os.environ/OPENAI_API_KEY litellm_settings: drop_params: true num_retries: 3 request_timeout: 600 cache: true cache_params: type: redis host: redis port: 6379 router_settings: routing_strategy: usage-based-routing-v2 # 按用量负载 fallbacks: - "gpt-5": ["claude", "qwen3-32b"] # GPT-5 失败 → fallback EOF # 启动 litellm --config config.yaml --port 4000

测试

from openai import OpenAI client = OpenAI(base_url="http://localhost:4000/v1", api_key="sk-...") # 用任意模型名 for model in ["qwen3-32b", "claude", "gpt-5"]: r = client.chat.completions.create( model=model, messages=[{"role": "user", "content": "hi"}], ) print(f"{model}: {r.choices[0].message.content}")

LiteLLM 内置特性

  • ✅ 自动 fallback / retry

  • ✅ Redis 缓存

  • ✅ 按 API Key 限流和计费

  • ✅ Prometheus metrics

  • ✅ Admin UI(用户管理、用量查看)

  • ✅ Slack / 邮件告警

5.4 关键设计点

5.4.1 模型映射策略
# 简单别名 "smart-fast": ["qwen3-32b"] # 按优先级 "smart-best": ["claude-opus-4-7", "gpt-5", "qwen3-32b"] # 第一个失败 → 第二个 → ... # 按权重负载 "smart": -model:"qwen3-32b" weight:70 -model:"claude" weight: 30
5.4.2 Fallback 策略
fallbacks = { "gpt-5": ["claude-sonnet-4-6", "qwen3-32b"], "claude-opus-4-7": ["gpt-5", "qwen3-32b"], }

典型场景

  • API rate limit 触发 → 切到自部署

  • 上游返回 5xx → 立即重试到备用

  • 超时 → 触发 fallback

5.4.3 缓存策略
cache: type: redis ttl: 3600 key_strategy: hash_of_prompt # 完全相同的 prompt 复用 excluded_models: ["claude-opus-4-7"] # 关键模型不缓存

注意

  • • 缓存对temperature > 0的请求风险大(用户期望随机性)

  • • 推荐只缓存temperature=0或带cache=true标记的请求

5.4.4 限流策略
rate_limits: -api_key:"sk-team-a" rpm:1000 # 每分钟请求 tpm:200000# 每分钟 token -api_key:"sk-team-b" rpm:500 tpm: 100000
5.4.5 计费
# LiteLLM 内置成本计算 model_costs = { "qwen3-32b": {"input": 0.0004, "output": 0.0012}, # per 1K tokens "claude-sonnet-4-6": {"input": 0.003, "output": 0.015}, "gpt-5": {"input": 0.010, "output": 0.040}, } # 每次请求记账 def calculate_cost(usage, model): return ( usage["prompt_tokens"] * model_costs[model]["input"] + usage["completion_tokens"] * model_costs[model]["output"] ) / 1000

5.5 监控面板

LiteLLM 内置 Admin UI,能看到:

  • 各模型 QPS / 错误率 / 延迟

  • 各 API Key 用量 / 成本

  • Fallback 触发次数

  • 缓存命中率

也可以接 Prometheus + Grafana 做更专业的监控。


六、生产环境的几个真实问题

6.1 长尾延迟治理

问题:99% 请求快,1% 请求 30s+。

对策

  • 设置严格超时(30-60 秒)

  • 长 prompt 走专门通道(不和短 prompt 混 batch)

  • 慢请求自动降级到小模型

6.2 成本失控

问题:业务突然爆量,月账单暴涨。

对策

  • API Key 配额硬上限(超额拒绝)

  • 实时成本监控 + 阈值告警

  • 大流量场景用便宜模型 + 缓存

  • 关键 / 极致需求才用顶级模型

6.3 多模型质量不一致

问题:业务有时用 GPT-5、有时 fallback 到 Qwen3-32B,效果差异大。

对策

  • 同等级模型才互相 fallback

  • prompt 适当冗余以适配多家模型

  • 通过 A/B 测试确认 fallback 模型质量

6.4 流式输出的代理坑

问题:流式输出经过代理后断了 / 慢了。

对策

# Nginx proxy_buffering off; proxy_cache off; proxy_read_timeout 600s; add_header X-Accel-Buffering no;
# FastAPI 代理 return StreamingResponse( generator(), media_type="text/event-stream", headers={"Cache-Control": "no-cache", "X-Accel-Buffering": "no"} )

七、扩展话题与下一篇预告

7.1 OpenAI 协议之外

虽然 OpenAI 兼容是事实标准,但有些场景需要别的协议:

  • MCP(Model Context Protocol):Anthropic 推出的 AI 工具协议,2024 年快速崛起

  • Anthropic Messages API:略有差异,但好处是支持更丰富的 system prompt

  • gRPC:内部高性能调用用,不走 HTTP

7.2 LLM Gateway 的未来

2026 年 LLM Gateway 的趋势:

  • 智能路由:根据 prompt 内容自动选最佳模型

  • Prompt 优化:网关层自动改写 prompt 节省 token

  • 语义缓存:相似 prompt 也能命中(不只是完全相同)

  • 观测增强:tracing 完整链路(OpenTelemetry 集成)

7.3 下一篇预告

  • 第 20 篇:分布式推理 - TP / PP / EP 并行策略详解—— 部署服务化篇的收官。我们已经多次提到 TP / PP / EP,这一篇会把它们彻底讲透。包括什么场景用什么、混合策略怎么设计、跨机部署的网络要求。

下一篇结束之后,部署服务化篇(第 16-20 篇)正式收官


八、结语:API 兼容性是 AI 工程化的基石

读完本文你应该明白:

  • OpenAI 兼容 API 是事实标准——一套客户端代码,N 个后端

  • 协议细节多——流式、Tool Use、JSON Mode、Vision 都有规范

  • 各框架"70-90% 兼容"——关键参数务必端到端测试

  • LLM Gateway 是生产必备——多模型、限流、计费、监控、容灾的统一入口

  • LiteLLM 是 2026 年开源首选——100+ 模型支持,生态最广

  • 长尾、成本、质量、流式四大生产问题都有套路化的对策

参考文献:

OpenAI 兼容 API 服务化:从协议到 LLM Gateway