OpenAI API 스트리밍 응답 처리하기
배경
지난주 공개된 ChatGPT API를 사내 챗봇에 적용하는 작업을 진행했다. 일반 REST API 호출로는 응답이 완전히 끝날 때까지 대기해야 해서 UX가 좋지 않았다. 스트리밍 방식으로 토큰 단위로 받아서 실시간으로 표시하는 구현이 필요했다.
구현
OpenAI API는 stream: true 옵션을 주면 SSE(Server-Sent Events) 방식으로 응답한다.
const response = await fetch('https://api.openai.com/v1/chat/completions', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.OPENAI_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
model: 'gpt-3.5-turbo',
messages: [{ role: 'user', content: prompt }],
stream: true,
}),
});
const reader = response.body.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
const chunk = decoder.decode(value);
const lines = chunk.split('\n').filter(line => line.trim() !== '');
for (const line of lines) {
if (line.startsWith('data: ')) {
const data = line.slice(6);
if (data === '[DONE]') continue;
const parsed = JSON.parse(data);
const content = parsed.choices[0]?.delta?.content;
if (content) {
// 클라이언트로 전송
res.write(`data: ${JSON.stringify({ content })}\n\n`);
}
}
}
}
이슈
청크가 중간에 잘려서 올 수 있어서 파싱 에러가 발생했다. 버퍼에 담아두고 완전한 JSON이 왔을 때만 파싱하도록 수정했다.
또한 Vercel의 serverless function 타임아웃(10초)을 고려해야 했다. 긴 응답의 경우 Edge Function으로 전환하는 것을 검토 중이다.
결과
응답 시작까지의 체감 속도가 크게 개선됐다. 사용자들이 답변이 생성되는 과정을 볼 수 있어서 만족도가 높아졌다.