使用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
相关产品推荐
相关产品推荐

