最省心的做法是构建统一AI网关中间层:通过ModelAdapter接口封装各平台鉴权、请求组装、响应解析与重试逻辑,配合路由表实现模型别名映射;集成Redis+Lua三级限流(模型/用户/场景)、主备自动降级、流式SSE逐块处理与实时Token计费。

直接用 PHP 调多家 AI 模型,最痛的不是写不通,而是每次换一家,就要改鉴权、重写参数结构、适配返回字段、手动加重试逻辑——业务代码里全是 if-else 和 vendor-specific 的 SDK。真正省心的做法,是把差异收进一个中间层,对外只暴露一套接口。
统一封装:用适配器+路由表抹平差异
别在控制器里直接 new GuzzleClient() 去调 OpenAI 或通义千问。建一个 ModelAdapter 接口,强制每个服务商实现:buildRequest()(组装 URL、headers、body)、parseResponse()(提取 content、tokens、error)、shouldRetry()(判断是否可重试)。再配一张路由表(比如 JSON 或数据库),定义:
-
模型别名:如
chat-pro、summarize-light - 真实后端:对应哪家平台、哪个 endpoint、用哪个 adapter 类
- 基础配置:默认 timeout、max_retries、是否启用流式
业务层只传 chat-pro 和消息数组,剩下的翻译、签名、token 刷新全由网关完成。通义千问要先换 access_token?封装在它的 adapter 里;Anthropic 返回的 usage 在 response body 里,OpenAI 在 header 里?各自 parseResponse 处理——上层完全无感。
限流:按模型、用户、场景三档控制
限流不是只防爆,更是控成本和保体验。PHP 层可用 Redis + Lua 做原子计数,分三级:
立即学习“PHP免费学习笔记(深入)”;
- 模型级:比如通义千问每分钟最多 60 次调用(防额度瞬间刷光)
- 用户级:VIP 用户每小时 1000 tokens,普通用户 200 tokens(结合 JWT 提取 user_id)
- 场景级:/api/chat 允许并发 5,/api/analyze/image 限制为 2(避免图像模型被文本请求拖垮)
注意:限流检查必须在路由分发前完成,且失败时返回标准错误(如 429 + JSON 提示),不能让请求走到下游再报错。
故障切换:主备自动降级,不卡住请求
主模型超时或连续 3 次 5xx,就切到备用模型。但切换不是简单换 URL:
- 先查路由表里该别名是否有
fallback_model配置 - 触发切换时记录 trace_id 和切换原因(方便审计)
- 降级后仍走同一套 adapter 流程,只是换了个实例——确保输出结构一致,业务不用改解析逻辑
- 设置“冷却时间”,比如 5 分钟内不再尝试主模型,避免抖动反复切换
关键点:故障切换必须异步通知运维(如发 Slack 消息),但不能阻塞当前请求。一次失败请求,应快速 fallback 并返回结果,而不是等主模型重试完再切。
流式响应与 Token 计费:别让 chunk 解析崩掉
启用了 stream: true,PHP 就得处理 SSE 格式(data: {...} 块)。别用 json_decode(file_get_contents())——它等完整响应,会卡死。正确做法:
- 用 cURL 的
CURLOPT_WRITEFUNCTION回调逐块接收 - 每收到一个
data:行,就 trim、decode、提取 content 字段并 echo 输出 - 遇到
data: [DONE]就结束流
计费也一样:不要等整个响应结束才算 token。在流式回调里,对每个 chunk 解析 usage 字段(如果存在),累加后存入 Redis 缓存,请求结束再落库。这样即使流中断,也有部分计费数据可追溯。



















