정신을 놓지 않고 스트리밍 AI 채팅 클라이언트를 구축했습니다
AI가 실시간으로 응답하는 채팅 인터페이스를 만들고 싶었습니다. 그 매끄러운 타이핑 효과(typewriter effect)를 구현하고 싶었죠.
생각보다 어려웠습니다. 문제는 AI가 아니었습니다. 문제는 API와 브라우저 사이의 파이프라인이었습니다.
이를 해결하기 위해 세 가지 다른 방법을 시도했습니다.
대기 방식 (The Wait Method) API를 호출하고 전체 응답이 올 때까지 기다린 후 화면에 표시했습니다. 작동은 했지만, UI가 몇 초 동안 멈춰버렸습니다. 사용자들은 앱이 고장 났다고 생각했고, "전송" 버튼을 반복해서 클릭했습니다. 이는 좋지 않은 사용자 경험이었습니다.
폴링 방식 (The Polling Method) 서버가 작업 ID(job ID)를 보내고, 클라이언트가 매초 업데이트를 요청하는 방식을 생각했습니다. 하지만 이는 과도한 서버 관리가 필요했습니다. 업데이트가 불규칙한 덩어리로 나타나 매끄럽지 않았습니다.
WebSocket 방식 (The WebSocket Method) Socket.IO를 시도해 보았습니다. 이는 엄청난 복잡성을 더했습니다. 재연결(reconnection), 하트비트(heartbeat), 상태 동기화(state synchronization)를 모두 관리해야 했습니다. 단순한 채팅 앱에 WebSocket은 과했습니다(overkill).
해결책은 더 간단했습니다: Server-Sent Events (SSE).
대부분의 AI API는 이미 HTTP를 통해 SSE로 응답을 보냅니다. 저는 복잡한 도구를 찾는 것을 그만두고 네이티브 fetch API를 사용했습니다.
response.body.getReader()를 사용하여 바이트 스트림을 직접 읽었습니다. SSE 프로토콜을 직접 파싱했습니다. 이 방식은 UI의 반응성을 유지하면서 표준 HTTP를 사용합니다.
작동 원리:
- WebSocket 서버가 필요 없습니다.
- 복잡한 재연결 로직이 필요 없습니다.
- SSE를 지원하는 모든 API에서 작동합니다.
AbortController를 사용하여 스트림을 쉽게 중단할 수 있습니다.
단점도 있습니다.
- 요청 없이는 클라이언트에 업데이트를 푸시할 수 없습니다.
- 연결이 끊어지면 부분적인 응답 데이터를 잃게 됩니다.
채팅 앱을 만든다면 양방향 통신이 필요한 경우가 아닌 한 WebSocket은 피하세요. HTTP 스트리밍을 사용하세요. 더 간단하고 신뢰할 수 있습니다.
여러분의 스트리밍 전략은 무엇인가요? WebSocket을 사용하시나요, 아니면 SSE를 사용하시나요? 댓글로 알려주세요.
출처: https://dev.to/__c1b9e06dc90a7e0a676b/i-built-a-streaming-ai-chat-client-without-losing-my-mind-3gi0