直接用 Yii2 的 yii\httpclient\Client 调用 ChatGPT API 不可行,因其默认不支持重试、SSE 流解析、自动注入 Authorization 头及限流头校验,遇 429 或 context_length_exceeded 等错误会直接崩溃。

直接用 Yii2 的 yii\httpclient\Client 调用 ChatGPT API 是可行的,但容易掉进认证、重试、流式响应解析、错误码处理这四个坑里——尤其是 429 Too Many Requests 和 context_length_exceeded 这类错误,不加封装会直接崩掉整个控制器。
为什么不能直接 new yii\httpclient\Client() 就发请求?
Yii2 自带的 HTTP 客户端默认不带重试、不解析 SSE 流、不自动注入 Authorization 头,也不校验 OpenAI 返回的 x-ratelimit-remaining-requests。你写一次 $client->post()->send(),遇到网络抖动或限流就挂,日志里只留个 429,根本没法恢复。
- 没设
timeout:OpenAI 流式响应可能卡在中间,PHP 进程等超时后直接 500 - 没处理
stream=True的 chunk:返回的是data: {json}行,不是纯 JSON,JsonParser会报错 - 没读取响应头里的
x-ratelimit-reset:下次重试时间全靠猜 - 硬编码
api_key在 action 里:一旦泄露,账单秒爆
如何封装一个可复用的 ChatGPTService 组件?
建议在 common/components/ 下建 ChatGPTService.php,用官方 openai/openai SDK(v1.0+)替代原生 HTTP 客户端——它已内置重试、token 计算、流式事件解析和错误分类。
- 初始化必须从环境变量读取
OPENAI_API_KEY,别用Yii::$app->params存密钥 - 构造函数里传入
model(如"gpt-4-turbo")和timeout(建议设为30) - 对外只暴露
chat()和streamChat()两个方法,内部统一做try/catch,把openaiSDK 的APIConnectionError、RateLimitError等转成 Yii2 可识别的异常 - 流式方法返回
Generator,每 yield 一个delta.content字符串,前端用 EventSource 接收即可
示例片段:
将小说章节转换为电影分镜剧本。用户上传txt/md/docx文本,AI分析场景、角色、情绪、镜头语言,输出专业分镜脚本。适用于用户提及“分镜”“storyboard”“小说转分镜”“影视改编”“镜头脚本”或需要将小说改编为分镜的场景。
use OpenAI\Client;
// ...
public function streamChat(array $messages): \Generator
{
$response = $this->client->chat()->create([
'model' => $this->model,
'messages' => $messages,
'stream' => true,
]);
<pre class='brush:php;toolbar:false;'>foreach ($response as $part) {
if ($part->choices[0]->delta->content ?? null) {
yield $part->choices[0]->delta->content;
}
}}
怎么避免 context_length_exceeded 错误?
OpenAI 的 token 计数和实际消耗存在偏差,尤其当 messages 含中文、emoji 或代码块时。tiktoken 库在 PHP 里没官方绑定,别自己写规则——直接用官方 SDK 的 countTokens() 方法(需传入 model 名)预估,再留出 200 token 余量。
- 对历史对话做截断时,优先删
role:assistant的旧回复,保留role:user的最新输入 - 别用
array_slice()粗暴砍数组,要按 token 数反向累加,直到总和 ≤max_tokens - 200 - 如果用户上传了文件内容,先用
mb_strimwidth()控制长度,再喂给模型 -
max_tokens参数别设满(比如 gpt-4-turbo 是 128k),否则模型没空间生成回复
生产环境必须加的三道防线
Yii2 项目上线后,光跑通就行不通了。OpenAI 的账单和稳定性是实打实的。
- 在
config/web.php的components里配cache(Redis),对相同 prompt + model 的响应缓存 300 秒,命中率超 65% 时能省 1/3 token 成本 - 用
yii\filters\RateLimiter在 controller 层控用户级 QPS,防恶意刷量;同时监听 OpenAI 响应头里的x-ratelimit-remaining-requests,动态降级(比如剩 2 次时切到 gpt-3.5-turbo) - 所有
messages和response日志必须脱敏:content字段只记前 20 字 +...[len=xxx],PII 信息(手机号、身份证)提前正则过滤
最易被忽略的点是:OpenAI 的 401 Unauthorized 错误不会触发 Yii2 的 errorHandler,因为它是 HTTP 层失败,得在 ChatGPTService 里主动 throw 新异常并 catch 到 controller 中 render 专用错误页——否则用户看到的是空白 500 页面。

















