PHP调用OpenAI GPT-3 API如何实现流式响应实时逐节点输出
问题根因
原代码无法实现流式输出,核心有3个问题:
- 开启了
CURLOPT_RETURNTRANSFER配置,cURL会把所有响应内容暂存到内存,等请求完全结束后才会一次性返回结果,不会边接收边处理 - 没有配置cURL的写入回调函数,无法在接收到每一块响应数据时立刻做处理输出
- PHP默认开启了输出缓冲,即使打印内容,Web服务器/PHP也会等内容攒到一定大小或者请求结束才发给浏览器,不会实时推送到前端
实现方案
按以下逻辑调整代码即可实现逐段实时输出:
- 关闭
CURLOPT_RETURNTRANSFER,通过写入回调逐块处理响应,不要等curl_exec返回完整结果 - 配置
CURLOPT_WRITEFUNCTION回调,每接收到一块OpenAI返回的SSE格式数据,立刻解析内容、推送给客户端 - 临时关闭PHP的输出缓冲,每次输出内容后手动执行刷缓冲操作,确保内容实时发送到前端
- Web访问场景下提前设置SSE相关响应头,关闭Web服务器的代理缓冲
调整后的可运行代码如下:
// 关闭PHP输出缓冲,开启强制刷出 ob_end_clean(); ob_implicit_flush(true); // Web场景下设置SSE响应头,关闭各级缓冲 header('Content-Type: text/event-stream'); header('Cache-Control: no-cache'); header('Connection: keep-alive'); header('X-Accel-Buffering: no'); // 适配Nginx反向代理场景,关闭Nginx缓冲 function str_starts_with_compatible($haystack, $needle) { return substr($haystack, 0, strlen($needle)) === $needle; } function openAI(){ $OPENAI_API_KEY="API_KEY_GOES_HERE"; $user_id="1"; $prompt="tell me what you can do for me."; $temperature=0.5; $max_tokens=300; $data = array( 'model'=>'text-davinci-002', 'prompt'=>$prompt, 'temperature'=>$temperature, 'max_tokens'=>$max_tokens, 'top_p'=>1.0, 'stream'=>true, 'frequency_penalty'=>0.0, 'presence_penalty'=>0.0, 'user' => $user_id ); $post_json= json_encode($data); $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, 'https://api.openai.com/v1/completions'); curl_setopt($ch, CURLOPT_RETURNTRANSFER, false); curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($ch, CURLOPT_POSTFIELDS, $post_json); curl_setopt($ch, CURLOPT_TIMEOUT, 0); // 取消超时限制,避免长流中断 $headers = array(); $headers[] = 'Content-Type: application/json'; $headers[] = "Authorization: Bearer $OPENAI_API_KEY"; $headers[] = 'Accept: text/event-stream'; curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); // 配置写入回调,每收到一块数据触发一次 curl_setopt($ch, CURLOPT_WRITEFUNCTION, function($ch, $chunk){ $lines = explode("\n", $chunk); foreach ($lines as $line) { $line = trim($line); if (empty($line)) continue; // 处理SSE格式的data行 if (str_starts_with_compatible($line, 'data: ')) { $dataStr = substr($line, 6); // 收到结束标识终止请求 if ($dataStr === '[DONE]') { return 0; } $data = json_decode($dataStr, true); if (isset($data['choices'][0]['text'])) { echo $data['choices'][0]['text']; // 刷出所有层级的缓冲 if (ob_get_level() > 0) { ob_flush(); } flush(); } } } return strlen($chunk); }); curl_exec($ch); curl_close($ch); } openAI();
注意事项
- 上述兼容函数
str_starts_with_compatible适配PHP 8以下版本,PHP 8及以上版本可以直接使用内置str_starts_with函数替换 - CLI命令行模式下运行不需要设置SSE相关HTTP头,仅关闭输出缓冲即可正常逐段输出
- 在Laravel、ThinkPHP等Web框架中使用时,需要提前关闭框架自带的响应缓冲机制,否则框架会在请求逻辑全部执行完成后才统一返回内容,流式输出会失效
- 写入回调中不要执行数据库写入、第三方接口调用等耗时操作,否则会导致流式响应卡顿中断
内容的提问来源于stack exchange,提问作者Logical Nonsense
相关产品推荐
相关产品推荐

