본문으로 건너뛰기

© 2026 Molayo

Dev.to헤드라인2026. 06. 22. 23:42

DefaultChatTransport를 언제 교체해야 할까요?

요약

Vercel AI SDK의 DefaultChatTransport가 가진 HTTP/SSE 기반의 설계 한계를 분석합니다. 프로덕션 환경에서 필요한 취소 기능, 스트림 재개, 멀티 디바이스 지원 등을 위해 WebSocket 기반 트랜스포트로 교체해야 하는 이유와 방법을 다룹니다.

핵심 포인트

  • DefaultChatTransport는 HTTP/SSE 기반의 단방향/점대점 방식임
  • 서버리스 환경에서 stop() 호출 시 서버 측의 실제 중단 처리가 어려움
  • 스트림 재개를 위해서는 Redis나 별도의 패키지 등 추가 설정이 필요함
  • WebSocket 기반 트랜스포트로 교체하여 내구성이 있는 세션 구축 가능

요약 (TL;DR): DefaultChatTransport는 HTTP POST와 SSE를 사용합니다. 이는 안정적인 연결을 가진 단일 사용자에게는 적합하지만, 서버에 도달하는 취소(cancellation) 기능, 멀티 디바이스 전달(multi-device delivery), Redis 없는 스트림 재개(stream resumption), 또는 멀티 유저 세션(multi-user sessions)이 필요한 프로덕션 환경에서는 설계 한계에 부딪힙니다. 이 포스트에서는 네 가지 한계점과 네 가지 질문으로 구성된 자가 진단, 그리고 WebSocket 기반의 세션 레이어가 추가하는 기능에 대해 다룹니다.

당신은 Vercel AI SDK를 사용하여 AI 채팅 앱을 구축했습니다. 개발 환경에서는 잘 작동합니다. 모델이 응답하고, 스트림이 전달되며, UI가 깔끔하게 업데이트됩니다. 하지만 프로덕션에 배포하면 트랜스포트 레이어(transport layer)의 한계가 드러나기 시작합니다.

이러한 실패의 대부분은 조용히 발생합니다. 데모에서는 잘 작동하다가, 어디를 살펴봐야 할지 알기 전까지는 파악하기 어려운 방식으로 문제가 발생합니다. 이들은 공통된 원인을 공유합니다. DefaultChatTransport는 HTTP를 위해 구축되었으며, HTTP는 일부 프로덕션 요구 사항을 초과하는 구조적 특성을 가지고 있습니다. 이 글에서는 그러한 한계가 무엇인지, 어떤 것이 귀하의 애플리케이션에 중요한지, 그리고 트랜스포트를 교체하는 것이 실제로 무엇을 의미하는지 설명합니다.

핵심 요약 (Key takeaways)

  • DefaultChatTransport는 HTTP POST와 Server-Sent Events (SSE)를 사용합니다. 이러한 프로토콜은 단방향(one-way)이며 점대점(point-to-point) 방식입니다. 이는 SDK의 버그가 아니라, 상태가 없는(stateless) 서버리스 플랫폼의 올바른 동작입니다.
  • stop()은 클라이언트 측에서 중단 신호(abort signal)를 발생시키고 즉시 반환됩니다. GitHub issue #9707 (open, 2025년 10월)에 따르면, 서버는 의도적인 중단과 연결 끊김을 구분할 수 없으며, 완료될 때까지 계속해서 생성 및 과금을 진행할 수 있습니다.
  • 공식적인 Vercel AI SDK의 스트림 재개(stream resumption) 패턴은 Redis, resumable-stream 패키지, 두 개의 커스텀 API 엔드포인트, 그리고 전용 중단 핸들러(stop handler)를 필요로 합니다. 재개 가능한 스트림(resumable stream) 설정에서 stop()은 취소(cancel)가 아닌 연결 끊김(disconnect)으로 취급됩니다.
  • ChatTransport 인터페이스는 설계 단계부터 교체 가능하도록(pluggable) 만들어졌습니다. Vercel의 서버리스 플랫폼은 지속적인 WebSocket 연결을 호스팅할 수 없기 때문에, 트랜스포트 계층을 교체할 수 있도록 설계했습니다. DefaultChatTransport를 WebSocket 기반의 트랜스포트 계층으로 교체하면, 에이전트, 도구 호출(tool calls), 또는 UI 렌더링을 변경하지 않고도 에이전트와 클라이언트 사이에 내구성이 있는 세션(durable session)을 생성할 수 있습니다.

DefaultChatTransport의 작동 방식 및 설계 조건

트랜스포트 옵션 없이 useChat()을 호출하거나 기본 설정을 전달하면 DefaultChatTransport가 실행됩니다. 이는 HTTP POST를 통해 나가는 메시지를 전송하고, 응답을 SSE 스트림으로 수신합니다.

안정적인 연결을 가진 단일 사용자가 메시지를 보내고 응답을 기다리는 상황이라면, 이것이 올바른 선택입니다. 상태가 없는(stateless) 서버리스 함수가 요청을 받고, 모델을 호출하며, 응답을 다시 스트리밍합니다. HTTP는 이를 위한 적절한 도구이며, DefaultChatTransport는 이를 올바르게 사용하고 있습니다.

그러한 동작은 플랫폼의 제약 사항에서 비롯됩니다. Vercel의 서버리스 함수(serverless functions)는 응답 후 종료되므로, 소켓(socket)을 열어둔 채 유지할 지속적인 프로세스가 없습니다. 이것이 네 가지 제한 사항 모두의 근본 원인입니다. 이는 설정 가능한 문제가 아니라 아키텍처적인 문제입니다. 상태가 없는(stateless) 플랫폼에서의 HTTP는 이러한 요구 사항을 단순히 수행할 수 없기 때문입니다. 전체적인 맥락을 파악하고 싶다면, Ably의 Vercel에서의 WebSockets 가이드에서 이 제약 사항을 심도 있게 다루고 있습니다.

이것이 바로 Vercel이 AI SDK 5에서 ChatTransport를 플러그인 방식으로 교체 가능하게(pluggable) 만든 이유이기도 합니다. DefaultChatTransport는 고장 난 것이 아닙니다. 그것이 구축된 조건 하에서는 올바르게 작동합니다. 하지만 Vercel은 팀들이 이러한 조건에 얽매이지 않는 전송 계층(transport)으로 교체할 수 있도록 인터페이스를 정밀하게 설계했습니다.

이러한 제약 사항은 DefaultChatTransport에만 국한되지 않습니다. 또 다른 내장 옵션인 DirectChatTransport조차도 "재연결할 지속적인 서버 측 스트림(server-side stream)이 없으므로 재연결을 지원하지 않는다"라고 명시적으로 문서화하고 있습니다. 재연결은 전송 계층(transport-layer)의 속성입니다. 기본 구현체들이 이를 갖추지 못한 이유는 그것들이 구축된 플랫폼이 이를 지원하지 않기 때문입니다.

DefaultChatTransport가 프로덕션 환경에서 할 수 없는 네 가지 작업

이것들은 단일 사용자 챗봇의 범위를 넘어설 때 나타나는 제한 사항들입니다. 예를 들어, 기기 간에 전환이 이루어지는 고객 지원 에이전트, 인간과 AI가 모두 참여하는 채팅 인터페이스, 또는 생성 도중 연결이 끊기는 것이 사용자에게 눈에 보이는 비용을 초래하는 모든 애플리케이션이 이에 해당합니다.

각각의 사례는 동일한 근본 원인에서 비롯됩니다. HTTP/SSE는 하나의 연결, 하나의 클라이언트, 하나의 응답을 위해 구축되었습니다. 프로덕션 환경에서 그 이상의 것을 요구할 때, 이러한 제약 사항이 명확히 드러나게 됩니다.

취소(Cancellation)가 모호하며, 이로 인해 비용을 지불하게 될 수도 있습니다. 사용자가 중지(stop)를 클릭하면, stop()은 클라이언트 측에서 HTTP 연결을 닫고 서버가 생성(generation)을 확인하거나 종료할 때까지 기다리지 않고 즉시 반환됩니다. 서버는 연결 종료 이벤트를 수신합니다. 하지만 서버는 이것이 탭을 닫은 것인지, 네트워크가 끊긴 것인지, 아니면 모바일 기기가 절전 모드로 들어간 것인지 구분할 방법이 없습니다. 따라서 서버는 계속해서 생성을 진행합니다.

GitHub issue #9707 (2025년 10월 제출, 현재 진행 중)은 이 문제를 직접적으로 문서화하고 있습니다: createUIMessageStream은 서버 측에서 중단 신호(abort signal)를 감지하지 못하며, 이로 인해 "진행 중인 AI 생성을 중단하는 것이 불가능하여 불필요한 비용 발생과 열악한 UX를 초래"합니다. GitHub issue #10844는 Vercel 자체의 supportsCancellation: true 설정 플래그가 프로덕션 배포 환경에서 신뢰할 수 없게 동작한다는 점을 추가로 지적합니다. 비용 문제는 실재합니다. 고아 생성(orphaned generations)이 완료될 때까지 계속 실행되며, 커스텀 서버 측 엔드포인트 없이는 이를 중단할 수 있는 신뢰할 만한 메커니즘이 없습니다.

멀티 디바이스 전달(Multi-device delivery)은 조용히 실패합니다. SSE는 일대일(one-to-one) 방식입니다. 하나의 HTTP 연결, 하나의 클라이언트, 하나의 스트림입니다. 노트북과 휴대폰에서 동일한 세션을 열어둔 사용자는 요청을 보낸 기기에서만 응답을 받게 됩니다. 두 번째 기기는 아무것도 받지 못합니다. 에러도, 부분적인 콘텐츠도, 무언가가 진행 중이라는 표시도 없습니다. 이것은 useChat 설정의 공백이 아닙니다. 이는 HTTP의 구조적 특성입니다. SSE는 설계상 일대일 방식이기 때문에, 대다수의 AI 전송(transport) 구현체에는 멀티 디바이스 팬아웃(fan-out) 기능이 결여되어 있습니다. DefaultChatTransport도 예외는 아닙니다.

동일한 아키텍처적 근원이 다음 한계점과 연결됩니다. 멀티 디바이스 전달이 HTTP가 제공할 수 없는 팬아웃(fan-out)을 요구하는 것처럼, 스트림 재개(stream resumption)는 HTTP가 유지할 수 없는 세션 지속성(session persistence)을 요구합니다.

스트림 재개(Stream resumption)는 직접 구축하고 소유해야 하는 인프라를 요구합니다. Vercel AI SDK 스트림 재개 문서에는 다음과 같은 전제 조건들이 직접적으로 나열되어 있습니다: Redis 인스턴스, resumable-stream 패키지, consumeSseStream을 사용하여 재개 가능한 스트림(resumable streams)을 생성하는 POST 핸들러, resumeExistingStream으로 이를 재개하는 /api/chat/[id]/stream 경로의 GET 핸들러, 그리고 전용 중단(stop) 엔드포인트가 필요합니다.

stop() 함수와 재개 가능한 스트림은 아키텍처 측면에서도 호환되지 않습니다. 문서는 이를 다음과 같이 직접적으로 명시합니다: "재개 가능한 스트림 설정에서 클라이언트 측 중단(aborts)은 연결 끊김(disconnects)으로 처리됩니다. 탭을 닫거나, 페이지를 새로고침하거나, stop()을 호출하는 것은 현재의 HTTP 연결만 닫을 뿐이며, 근본적인 생성(underlying generation)을 취소해서는 안 됩니다." 제대로 작동하는 중단 버튼을 추가하려면, 근본적인 작업을 취소하고 활성 스트림 레코드를 삭제하기 위한 별도의 서버 측 엔드포인트가 필요합니다.

탭 전환(Tab switches)과 모바일 백그라운드 전환(mobile backgrounding)은 페이지 새로고침과는 다른 방식으로 resumable-stream 패턴이 커버하지 못하는 또 다른 격차입니다. Vercel AI SDK 재개 가능한 스트림에 관한 Ably 가이드에서 이러한 차이점을 다루고 있습니다.

단일 응답 가정은 다중 사용자 세션(multi-user sessions)을 깨뜨립니다. Vercel은 한 명의 사용자가 하나의 메시지를 보내고 하나의 응답을 받는 상황을 기준으로 useChat을 설계했습니다. 이는 한 번에 하나의 activeResponse를 추적합니다. 만약 두 번째 사용자가 참여하거나, 관찰자 기기(observer device)가 동일한 응답 생명주기(lifecycle)를 필요로 하는 경우, 사용 가능한 유일한 메커니즘은 setMessages뿐입니다. 이는 생명주기 훅(lifecycle hooks), 도구 호출 알림(tool-call notifications), 그리고 onFinish 콜백을 완전히 우회합니다. 작동은 하지만, 이는 임시방편(workaround)입니다. Ably 트랜스포트 구축에 관한 Zak Knill의 포스트에서 구현 세부 사항을 다룹니다.

위에서 언급한 네 가지 제한 사항은 각각 동일한 근본 원인을 가지고 있지만 서로 다르게 나타납니다. 아래 표는 이를 실제 운영 비용(production cost)과 매핑하여 보여줍니다:

제한 사항 (Limit)발생하는 문제 (What breaks)운영 비용 (Production cost)DefaultChatTransport에서 설정 가능 여부?
취소 (Cancellation)서버가 중단(stop)과 연결 끊김(disconnect)을 구분할 수 없음고아 생성 (Orphaned generations); 지속적인 과금 발생아니오 (No)
...

WebSocket 기반 전송 계층이 에이전트와 클라이언트 간의 지속적인 세션을 생성하는 방법

DefaultChatTransport를 WebSocket 기반 전송 계층으로 교체하면, 상태가 없는 (stateless) HTTP 연결이 에이전트와 사용자 간의 지속적인 (durable) 세션으로 대체됩니다. 이 세션은 단일 연결을 넘어 유지되며 네 가지 제한 사항을 모두 직접적으로 해결합니다. 또한, 이러한 제한 사항들로 인해 강제되었던 커스텀 인프라 구축의 필요성도 제거합니다. 커스텀 ChatTransport 구현에 관한 Ably 주제 페이지에서 전체적인 기능 범위를 다루고 있습니다. 이 섹션에서는 여러분의 백로그(backlog)에서 무엇이 사라지는지를 다룹니다.

WebSocket 기반 전송 계층을 사용하면 더 이상 다음이 필요하지 않습니다:

  • 재개 가능한 스트림 (resumable streams)을 위한 Redis 버퍼
  • 경합 조건 (race condition) 방지 기능이 포함된 중단 (stop) 엔드포인트
  • 멀티 디바이스 전달을 위한 팬아웃 (fan-out) 계층
  • 멀티 유저 세션을 위한 setMessages 우회 방법 (workaround)

How a durable session works: session decoupled from connection, showing cancel signal and reconnect from position

Ably AI Transport는 프로덕션용 AI 애플리케이션을 위한 세션 계층으로 구축되었습니다. 즉, DefaultChatTransport가 처리할 수 없는 전달 관련 문제들을 처리하는 에이전트와 사용자 사이의 인프라입니다. 이는 단 한 번의 설정 변경을 통해 ChatTransport 구현체로서 useChat에 연결됩니다:

// 이전: 기본 HTTP 전송
const { messages, sendMessage, stop } = useChat({
  transport: new DefaultChatTransport({ api: '/api/chat' }),
...

변하지 않는 것: 에이전트(agent), 도구 호출(tool calls), 메시지 지속성 로직(message persistence logic), 그리고 UI 렌더링입니다. 교체되는 것은 useChattransport 옵션입니다. 그 위에 구축된 모든 것은 그대로 유지됩니다.

자체 턴(own-turns), 관찰자 턴(observer-turns), 그리고 setMessages 처리에 대한 구현 세부 사항은 Zak Knill의 포스트를 참조하세요. 전반적인 전송(transport) 옵션 비교에 대해서는 Vercel AI SDK 애플리케이션을 위한 지속성 세션 가이드를 확인하시기 바랍니다. 다음 섹션의 네 가지 질문은 여러분이 아직 그 결정 단계에 도달했는지 판단하는 데 도움이 될 것입니다.

DefaultChatTransport가 여전히 올바른 선택인 경우

위에서 언급한 네 가지 한계는 실재하지만, 서버에 도달하는 취소(cancellation) 기능, 멀티 디바이스 전달(multi-device delivery), 페이지 새로고침 이상의 스트림 재개(stream resumption), 또는 동일한 대화 내의 다수 사용자 기능이 필요한 경우에만 차단 요소가 됩니다. 많은 애플리케이션에서 DefaultChatTransport는 여전히 적절한 시작점입니다.

자신의 상황을 평가하는 실질적인 방법은 다음 네 가지 질문을 검토하는 것입니다:

  1. UI 업데이트뿐만 아니라 실제 모델 호출까지 서버 측 생성을 확실하게 취소하기 위해 stop() 기능이 필요한가요?
  2. 사용자가 하나 이상의 디바이스나 탭에서 동일한 세션에 접속하나요?
  3. 단순한 전체 페이지 새로고침이 아니라, 탭 전환이나 모바일 백그라운드 전환 시에도 스트림 재개(stream resumption)가 필요한가요?
  4. 한 대화에 둘 이상의 사용자가 참여하나요?

네 가지 질문 모두에 대한 답변이 '아니오'라면, DefaultChatTransport는 타당한 선택입니다. 만약 하나라도 '예'라면, 위의 관련 섹션에서 여러분이 직면하게 될 구체적인 한계에 대해 설명하고 있습니다. 전송(transport)을 교체해야 할 적절한 시점은 이러한 한계로 인해 비용(문제)이 발생하기 시작할 때입니다.

AI 자동 생성 콘텐츠

본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
0

댓글

0