如何用PHP与JavaScript通过SSE流式传输OpenAI API数据并修复报错?
问题描述
PHP端报错问题
用PHP通过SSE流式传输OpenAI API数据时,修改的CURLOPT_WRITEFUNCTION回调代码触发报错:
Notice: Trying to access array offset on value of type null in C:\FILE_PATH\sse.php.
原PHP代码片段:
curl_setopt($ch, CURLOPT_WRITEFUNCTION, function ($curl, $data) { # str_repeat(' ',1024*8) is needed to fill the buffer and will make streaming the data possible $data = json_decode($data, true); $text = $data['choices'][0]['text']; echo $text . str_repeat(' ', 1024 * 8); return strlen($data); });
客户端流式渲染问题
不知道如何将OpenAI返回的text内容实时渲染到页面元素中,当前JavaScript实现如下:
$.ajax({ type: "POST", url: "sse.php", data: JSON.stringify({ prompt: "What is the best way to", num_completions: 1, temperature: 0.5, }), contentType: "application/json", success: function (response) { const source = new EventSource("sse.php"); source.onmessage = function (event) { const div = document.getElementById("response"); div.innerHTML += event.data + "<br>"; console.log(event); }; }, });
OpenAI API流式返回数据示例:
data: {"id": "cmpl-XXXXXXXXXXXXXXXXXXXXXXX", "object": "text_completion", "created": 1671700494, "choices": [{"text": " Best", "index": 0, "logprobs": null, "finish_reason": null}], "model": "text-davinci-003"} data: {"id": "cmpl-XXXXXXXXXXXXXXXXXXXXXXX", "object": "text_completion", "created": 1671700494, "choices": [{"text": " way", "index": 0, "logprobs": null, "finish_reason": null}], "model": "text-davinci-003"} data: {"id": "cmpl-XXXXXXXXXXXXXXXXXXXXXXX", "object": "text_completion", "created": 1671700494, "choices": [{"text": " to", "index": 0, "logprobs": null, "finish_reason": null}], "model": "text-davinci-003"} data: [DONE]
解决方案
一、修正PHP端代码
报错原因是OpenAI返回的流式数据包含data: [DONE]这类非JSON格式的结尾标记,直接json_decode会得到null,导致数组访问报错。需要先过滤无效数据,再安全提取text字段:
// 先设置SSE响应头 header('Content-Type: text/event-stream'); header('Cache-Control: no-cache'); header('Connection: keep-alive'); ob_implicit_flush(true); // 禁用输出缓冲 // 修正后的CURLOPT_WRITEFUNCTION回调 curl_setopt($ch, CURLOPT_WRITEFUNCTION, function ($curl, $chunk) { $chunk = trim($chunk); // 跳过空行和结束标记 if (empty($chunk) || $chunk === 'data: [DONE]') { return strlen($chunk); } // 剥离前缀的"data: " $jsonStr = substr($chunk, 6); $data = json_decode($jsonStr, true); // 安全检查数据结构 if (isset($data['choices'][0]['text'])) { $text = $data['choices'][0]['text']; // 按照SSE标准格式输出 echo "data: " . $text . "\n\n"; // 强制刷新缓冲区 ob_flush(); flush(); } return strlen($chunk); });
二、修正客户端JavaScript代码
原代码的问题是用POST请求后再创建EventSource,但EventSource默认是GET请求,无法传递POST参数。推荐用Fetch API配合ReadableStream实现流式接收(支持POST):
const responseDiv = document.getElementById("response"); fetch('sse.php', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ prompt: "What is the best way to", num_completions: 1, temperature: 0.5, }) }) .then(response => { const reader = response.body.getReader(); const decoder = new TextDecoder('utf-8'); function readStream() { reader.read().then(({ done, value }) => { if (done) return; // 解析流式返回的SSE数据 const chunk = decoder.decode(value, { stream: true }); const lines = chunk.split('\n\n'); lines.forEach(line => { if (line.startsWith('data: ') && line !== 'data: [DONE]') { const text = line.substring(6); responseDiv.textContent += text; // 用textContent避免HTML注入,需格式化可改用innerHTML } }); readStream(); }); } readStream(); }) .catch(error => { console.error('请求失败:', error); });
如果坚持用EventSource(仅支持GET),需调整PHP接收参数方式:
const responseDiv = document.getElementById("response"); const params = new URLSearchParams({ prompt: "What is the best way to", num_completions: 1, temperature: 0.5, }); const source = new EventSource(`sse.php?${params}`); source.onmessage = function(event) { if (event.data !== '[DONE]') { responseDiv.textContent += event.data; } else { source.close(); // 结束流 } }; source.onerror = function(error) { console.error('SSE错误:', error); source.close(); };
对应PHP端从$_GET取参数:
$prompt = $_GET['prompt'] ?? ''; $num_completions = $_GET['num_completions'] ?? 1; $temperature = $_GET['temperature'] ?? 0.5;
关键说明
- OpenAI流式返回的每一行都以
data:开头,最后一行是data: [DONE],必须过滤这些标记才能提取有效文本。 - PHP端需强制刷新输出缓冲区,确保数据实时发送到客户端。
- Fetch API的ReadableStream更适合处理POST请求的流式响应,
EventSource仅适合简单GET场景。
内容的提问来源于stack exchange,提问作者cal ibra
相关产品推荐
相关产品推荐

