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

使用Axios接收OpenAI API流式响应时JSON解析失败问题排查

使用Axios调用OpenAI流式API时JSON解析失败的问题

我尝试用Axios调用OpenAI API,设置stream=true获取流式响应(返回多个JSON片段)。在Postman里请求正常,能读取每个独立的JSON对象,但Axios里没法成功解析JSON。

请求函数代码

return axios
        .post('https://api.openai.com/v1/chat/completions', {
            model: "gpt-4",
            messages: messages,
            stream: true,
        }, {
            headers: {
                'Authorization': 'Bearer ' + secretKey,
                'accept': 'application/json',
                'content-type': 'application/json',
            },
            responseType: 'stream',
        })

流式读取代码

const stream = await streamChatGPTResponseForOpenChat([], 'What is science?', null)

stream.data.on('data', (data: Buffer) => {
                try {
                    const str = data.toString('utf8'); //读取结果为"data: {'foo': 'bar', ... }"

                    //曾尝试手动解析,但部分块失败:
                    // console.log(str);
                    // const startIndex = str.indexOf('{');
                    // const endIndex = str.lastIndexOf('}') + 1;

                    //当前尝试方案,但约30%的块解析失败,且第一个块必失败
                    const json = JSON.parse(str.replace('data: ', ''));

                    const msg = json.choices[0].delta.content;
                    console.log(msg);

                    if (msg.includes('\n')) {
                        console.log('-----');

                        messages.push(currentMessage);
                        currentMessage = '';
                    }

                currentMessage = currentMessage.concat(msg);

                expect(data).to.be.a.string;
            } catch (error) {
                console.log('error: ' + error);
                console.log(data.toString());
                reject(error);
            }
        });

        stream.data.on('end', () => {
            console.log('DONE');
            resolve('done');
        });

遇到的错误

  • 错误1:第一个块必现SyntaxError: Unexpected token d in JSON at position 198,指向Axios请求文件;
  • 错误2:随机出现SyntaxError: Unexpected token d in JSON at position 195,在on('data')函数中捕获,但待解析的JSON看似正确。

我猜测是Buffer转换为数据的方式有问题,请问我的实现里存在哪些问题?


问题分析与解决方案

你的核心问题在于对OpenAI流式响应的格式处理不严谨,以及Axios流式传输中data事件的特性没把握好,具体问题点和解决方法如下:

1. 忽略了**Server-Sent Events(SSE)**的分隔规则

OpenAI的流式响应采用SSE格式,每个数据块的结构是:

  • 以data: 开头,后跟JSON片段
  • 每个完整的JSON片段后会有\n\n作为分隔符
  • 最后一个块是data: [DONE]表示结束

你直接替换data: 就解析,没考虑以下情况:

  • 单个data事件可能返回多个SSE块(比如data: {...}\n\ndata: {...})
  • 单个SSE块可能被拆分成多个data事件返回(比如一次只传了一半JSON)
  • 第一个块可能包含额外空白或结束标记

2. 错误的Buffer处理逻辑

你直接把每个data事件的Buffer转成字符串就解析,但Buffer可能只是完整JSON片段的一部分,或者包含多个完整片段,导致JSON.parse失败。

3. 未处理结束标记[DONE]

当响应结束时,OpenAI会返回data: [DONE],你没做判断就直接解析,这必然触发JSON语法错误(第一个块失败大概率是因为包含了这个标记或者不完整的片段)。

修复后的代码示例

const stream = await streamChatGPTResponseForOpenChat([], 'What is science?', null);

// 缓存不完整的片段
let buffer = '';

stream.data.on('data', (data: Buffer) => {
    try {
        buffer += data.toString('utf8');
        // 按SSE分隔符拆分所有完整块
        const chunks = buffer.split('\n\n');
        // 最后一个元素可能是不完整的,重新放回缓存
        buffer = chunks.pop() || '';

        for (const chunk of chunks) {
            if (!chunk.trim()) continue; // 跳过空行

            // 移除data:前缀
            const jsonStr = chunk.replace(/^data: /, '').trim();
            
            // 处理结束标记
            if (jsonStr === '[DONE]') {
                console.log('DONE');
                resolve('done');
                return;
            }

            const json = JSON.parse(jsonStr);
            const msg = json.choices[0].delta.content;
            
            if (msg) { // 注意delta可能没有content(比如开头的系统消息)
                console.log(msg);
                if (msg.includes('\n')) {
                    console.log('-----');
                    messages.push(currentMessage);
                    currentMessage = '';
                }
                currentMessage = currentMessage.concat(msg);
            }
        }
    } catch (error) {
        console.log('error: ' + error);
        console.log('当前缓存内容:', buffer);
        // 不要直接reject,避免单个错误中断整个流
    }
});

stream.data.on('end', () => {
    // 处理最后剩余的缓存(如果有的话)
    if (buffer.trim()) {
        try {
            const jsonStr = buffer.replace(/^data: /, '').trim();
            if (jsonStr !== '[DONE]') {
                const json = JSON.parse(jsonStr);
                const msg = json.choices[0].delta.content;
                if (msg) {
                    currentMessage = currentMessage.concat(msg);
                    messages.push(currentMessage);
                }
            }
        } catch (e) {
            console.log('结束时解析剩余内容出错:', e);
        }
    }
    console.log('STREAM END');
    resolve('done');
});

额外注意事项

  • 移除expect(data).to.be.a.string断言,data是Buffer类型,该断言必然失败;
  • 对delta.content做非空判断,响应的部分块可能只包含role字段,没有内容;
  • 错误处理不要直接reject(error),否则单个块解析失败会中断整个流,应记录错误后继续处理后续块。

内容的提问来源于stack exchange,提问作者Jacobo Koenig

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.21 11:00:23