【学习笔记】OpenAI 兼容 API 服务化:从协议到 LLM Gateway(19/35)
前面几篇我们反复提到OpenAI 兼容 API——vLLM、SGLang、Ollama、TGI、lmdeploy 全都支持它。为什么这件事这么重要举个真实的工程场景你的团队同时在用闭源 APIClaude、GPT-5、Gemini、DeepSeek API自部署 vLLM内网 Qwen3-32B自部署 SGLangAgent 业务用本地 Ollama开发同学的笔记本未来还要接阿里通义、Moonshot、智谱如果每家都用自己的 SDK你的客户端代码要维护 N 套。但如果它们都长得像 OpenAI API你的客户端只用openai库就够了——只需要改base_url和model参数。这就是 OpenAI 兼容 API 的事实标准价值。这一篇我们要彻底搞清这个事OpenAI API 协议的关键设计各家推理框架的兼容程度和差异点怎么自己实现一个 OpenAI 兼容代理怎么设计 LLM Gateway 统一多模型 计费 限流 缓存读完本文你将能写出符合 OpenAI 协议的兼容 API 实现选对 LLM Gateway 工具LiteLLM / Portkey / 自研在生产环境跑一个多模型路由 容灾 计费 缓存的网关我们开始。一、OpenAI 兼容 API怎么变成「事实标准」的1.1 历史回顾2020.06GPT-3 API 发布OpenAI 定义了/v1/completions接口2023.03ChatGPT API 上线/v1/chat/completions接口诞生2023.06vLLM 第一次提供「OpenAI 兼容 API」选项2023 下半年所有主流推理框架跟进2024Anthropic、Google 等对手也开始提供 OpenAI 兼容接口兼容层2026OpenAI 兼容已是行业默认为什么是 OpenAI 的协议被「奉为标准」先发优势GPT-3 / ChatGPT 火得早大家先按它对接设计合理协议简洁清晰支持流式 / Tool Use / 多模态扩展生态压力openaiPython SDK 太流行所有客户端都用它社区共识大家都不想发明新协议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 流式输出SSEstream: true时返回 Server-Sent Eventsdata: {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]两个关键点每行以data:开头空行分隔最后以data: [DONE]结束Python 客户端处理from openai import OpenAI client OpenAI(base_url..., api_key...) stream client.chat.completions.create( modelqwen3-32b, messages[{role: user, content: hi}], streamTrue, ) for chunk in stream: delta chunk.choices[0].delta.content or print(delta, end, flushTrue)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 多模态VisionOpenAI 在 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 OutputsJSON 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 兼容性对比特性vLLMSGLangTGIOllamalmdeploy/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 框架不识别某框架对temperature0处理与 OpenAI 不一致 → 输出不稳定对策用openai官方 Python SDK 测试能跑通才能上线关键参数temperature / max_tokens / stream必须做端到端测试灰度上线监控成功率四、自己实现一个 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(titleLLM 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, fModel {model} not found) route ROUTING[model] backend_body {**body, model: route[real_model]} stream body.get(stream, False) # 记录请求 start_time time.time() request_id freq-{int(start_time*1000)} if stream: return StreamingResponse( forward_stream(route, backend_body, request_id, user_key), media_typetext/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, connect10.0) asyncwith httpx.AsyncClient(timeouttimeout) as client: try: resp await client.post( f{route[url]}/v1/chat/completions, jsonbody, 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(contentdata) 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, connect10.0) total_tokens 0 asyncwith httpx.AsyncClient(timeouttimeout) as client: asyncwith client.stream( POST, f{route[url]}/v1/chat/completions, jsonbody, headers{Content-Type: application/json}, ) as resp: if resp.status_code ! 200: err await resp.aread() yieldfdata: {{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, host0.0.0.0, port8000)4.3 启动 测试# 启动 API_KEYSsk-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 的核心职责统一接口所有应用看到的都是 OpenAI 标准多后端路由按模型名 / 优先级 / 业务路由鉴权API Key 管理 配额限流QPS / TPM 限制计费token 使用 → 成本核算缓存相同请求复用结果监控延迟、错误率、成本可视化容灾上游失败自动 fallback审计日志、合规审查5.2 主流 Gateway 方案对比方案类型优势适合LiteLLM开源 Python100 模型支持社区活跃首选⭐Portkey商业 SaaS监控强、可视化好企业Kong AI Gateway商业 / 开源与 Kong API Gateway 集成已用 KongCloudflare AI Gateway云服务全球节点 / 边缘缓存全球应用自研自己写完全可控大厂5.3 LiteLLM 实战LiteLLM 是 2024 年开源社区最火的 Gateway它把 100 模型 API 都映射到 OpenAI 协议。部署 LiteLLM Proxypip 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_urlhttp://localhost:4000/v1, api_keysk-...) # 用任意模型名 for model in [qwen3-32b, claude, gpt-5]: r client.chat.completions.create( modelmodel, 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: 305.4.2 Fallback 策略fallbacks { gpt-5: [claude-sonnet-4-6, qwen3-32b], claude-opus-4-7: [gpt-5, qwen3-32b], }典型场景API rate limit 触发 → 切到自部署上游返回 5xx → 立即重试到备用超时 → 触发 fallback5.4.3 缓存策略cache: type: redis ttl: 3600 key_strategy: hash_of_prompt # 完全相同的 prompt 复用 excluded_models: [claude-opus-4-7] # 关键模型不缓存注意• 缓存对temperature 0的请求风险大用户期望随机性• 推荐只缓存temperature0或带cachetrue标记的请求5.4.4 限流策略rate_limits: -api_key:sk-team-a rpm:1000 # 每分钟请求 tpm:200000# 每分钟 token -api_key:sk-team-b rpm:500 tpm: 1000005.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] ) / 10005.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效果差异大。对策同等级模型才互相 fallbackprompt 适当冗余以适配多家模型通过 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_typetext/event-stream, headers{Cache-Control: no-cache, X-Accel-Buffering: no} )七、扩展话题与下一篇预告7.1 OpenAI 协议之外虽然 OpenAI 兼容是事实标准但有些场景需要别的协议MCPModel Context ProtocolAnthropic 推出的 AI 工具协议2024 年快速崛起Anthropic Messages API略有差异但好处是支持更丰富的 system promptgRPC内部高性能调用用不走 HTTP7.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

相关新闻

最新新闻

日新闻

周新闻

月新闻