You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用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;
关键说明
  1. OpenAI流式返回的每一行都以data: 开头,最后一行是data: [DONE],必须过滤这些标记才能提取有效文本。
  2. PHP端需强制刷新输出缓冲区,确保数据实时发送到客户端。
  3. Fetch API的ReadableStream更适合处理POST请求的流式响应,EventSource仅适合简单GET场景。

内容的提问来源于stack exchange,提问作者cal ibra

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.07 06:25:22