"왜 AI는 답변을 한 번에 주지 않고, 타이핑하듯 한 글자씩 보여줄까?"
ChatGPT, Claude, Gemini 등 생성형 AI 서비스를 사용하다 보면 공통적으로 눈에 띄는 현상이 있다.
질문을 던지면 답변이 한꺼번에 "짠!" 하고 나타나는 게 아니라, 마치 누군가 실시간으로 타이핑하는 것처럼 글자가 하나씩 흘러나온다.
처음에는 단순히 "사용자 경험을 위한 애니메이션 효과인가?" 싶었다. 하지만 조금 더 생각해보니 의문이 들었다.
먼저 생성형 AI의 근본적인 동작 원리를 이해할 필요가 있었다.
대형 언어 모델(LLM)은 자기회귀적(autoregressive) 방식으로 텍스트를 생성한다.
❌ 전체 문장을 한 번에 만들어서 출력
✅ 다음에 올 토큰 1개를 예측 → 붙이기 → 반복
예를 들어 "Kafka는 어떻게 동작하나요?"라는 질문에 답할 때:
| Step | 현재까지의 텍스트 | 예측된 다음 토큰 |
|---|---|---|
| 1 | Kafka는 | 분산 |
| 2 | Kafka는 분산 | 메시지 |
| 3 | Kafka는 분산 메시지 | 브로커 |
| 4 | Kafka는 분산 메시지 브로커 | 입니다 |
| ... | ... | ... |
이 과정이 종료 토큰이 나올 때까지 수백~수천 번 반복된다.
사용자 경험(UX)
이론은 알겠는데, 실제로 그렇게 동작하는지 직접 눈으로 확인하고 싶었다.
ChatGPT는 SSE(Server-Sent Events) 프로토콜을 사용하고 있었다.
Request Headers (클라이언트 → 서버):
accept: text/event-stream
content-type: application/json

Response Headers (서버 → 클라이언트):
content-type: text/event-stream; charset=utf-8
cache-control: no-store
text/event-stream이 바로 SSE 프로토콜의 시그니처다.
SSE는 서버에서 클라이언트로 단방향 실시간 데이터 스트리밍을 가능하게 하는 웹 표준 기술이다.
| 특징 | 설명 |
|---|---|
| 방향 | 서버 → 클라이언트 (단방향) |
| 연결 | HTTP 연결 유지 (keep-alive) |
| 형식 | 텍스트 기반, 간단한 구조 |
| 재연결 | 자동 재연결 지원 |
WebSocket과 비교하면:
| 구분 | SSE | WebSocket |
|---|---|---|
| 방향 | 단방향 | 양방향 |
| 프로토콜 | HTTP | WS/WSS |
| 복잡도 | 낮음 | 높음 |
| 사용 사례 | 알림, 스트리밍 | 채팅, 게임 |
AI 응답 스트리밍은 서버→클라이언트 단방향이므로 SSE가 적합하다.
event: delta
data: {"v": "아주"}
event: delta
data: {"v": " 좋은 관"}
event: delta
data: {"v": "찰입니다"}
event: delta
data: {"v": ". \n많은 사람들이"}
event: delta
data: {"v": " **"왜 한 번에 결과"}
실제 전송 단위를 보면 정확히 한 글자씩이 아니다:
| 순서 | 전송된 토큰 |
|---|---|
| 1 | "아주" |
| 2 | " 좋은 관" |
| 3 | "찰입니다" |
| 4 | ". \n많은 사람들이" |
| 5 | " **"왜 한 번에 결과" |
토큰은 단어, 서브워드, 또는 여러 글자의 조합일 수 있다. 한국어의 경우 음절이나 형태소 경계에서 분리되는 경향이 있어 "글자 단위"처럼 보이는 것이다.
SSE 요청의 경우 EventStream이라는 특별한 탭이 나타난다

이 탭에서 실시간으로 전송되는 delta 이벤트들을 확인할 수 있다.
생성형 AI는 전체 문장을 한 번에 만드는 게 아니라, 다음 토큰을 하나씩 예측하며 순차적으로 생성한다. 이것이 자기회귀(autoregressive) 생성 방식이다.
토큰이 생성될 때마다 즉시 사용자에게 전송하면:
ChatGPT는 SSE(Server-Sent Events) 프로토콜을 사용한다:
accept: text/event-streamcontent-type: text/event-streamevent: delta + data: {"v": "토큰"}