OpenAI API 스트리밍 응답 처리하기

문제 상황

챗봇 기능을 구현하면서 OpenAI API의 응답 대기 시간이 UX 문제가 되었다. 특히 긴 답변의 경우 사용자가 10초 이상 빈 화면을 보게 되는 상황이 발생했다.

스트리밍 적용

OpenAI API는 stream: true 옵션으로 SSE(Server-Sent Events) 방식의 스트리밍을 지원한다.

// 백엔드 (Express)
app.post('/api/chat', async (req, res) => {
  res.setHeader('Content-Type', 'text/event-stream');
  res.setHeader('Cache-Control', 'no-cache');
  res.setHeader('Connection', 'keep-alive');

  const stream = await openai.chat.completions.create({
    model: 'gpt-4',
    messages: req.body.messages,
    stream: true,
  });

  for await (const chunk of stream) {
    const content = chunk.choices[0]?.delta?.content || '';
    res.write(`data: ${JSON.stringify({ content })}\n\n`);
  }

  res.end();
});
// 프론트엔드 (React)
const fetchStream = async (messages: Message[]) => {
  const response = await fetch('/api/chat', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ messages }),
  });

  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.startsWith('data:'));

    lines.forEach(line => {
      const data = JSON.parse(line.slice(5));
      setResponse(prev => prev + data.content);
    });
  }
};

결과

첫 토큰이 1초 내에 표시되면서 체감 속도가 크게 개선되었다. 에러 핸들링과 타임아웃 처리는 추가 작업이 필요하지만, 기본적인 스트리밍 구조는 안정적으로 동작했다.