챗봇 스트림 재개
챗봇 스트림 재개 (Chatbot Resume Streams)
useChat는 페이지 리로드 후 진행 중인 스트림을 재개하는 것을 지원합니다. 이 기능으로 장기 실행 생성이 있는 애플리케이션을 구축할 수 있습니다.
재개 가능한 스트림 설정에서 클라이언트측 중단(abort)은 연결 끊김(disconnect)으로 취급됩니다. 탭을 닫거나, 페이지를 새로고침하거나, stop()을 호출하는 것은 현재 HTTP 연결만 닫고 기본 생성(underlying generation)은 취소해서는 안 됩니다. 사용자가 생성을 중단하게 하려면 부분 응답을 저장하고 활성 작업을 취소하며 활성 스트림을 정리하는 전용 중지 엔드포인트를 추가하세요.
출처: 공식문서
본문
스트림 재개가 동작하는 방식
스트림 재개는 애플리케이션에 메시지와 활성 스트림의 영속화를 요구합니다. AI SDK는 저장소에 연결하는 도구를 제공하지만, 저장소 자체는 직접 구성해야 합니다.
AI SDK가 제공하는 것:
- 활성 스트림에 자동으로 다시 연결하는
useChat의resume옵션 consumeSseStream콜백을 통한 나가는 스트림 접근- resume 엔드포인트로의 자동 HTTP 요청
직접 구축해야 하는 것:
- 어떤 스트림이 어느 채팅에 속하는지 추적하는 저장소
- UIMessage 스트림을 저장하는 Redis
- 두 API 엔드포인트: 생성용 POST, 재개용 GET
- Redis 저장소 관리를 위한
resumable-stream통합
전제 조건
resumable-stream패키지 — 스트림의 발행/구독 메커니즘을 처리합니다.- Redis 인스턴스 — 스트림 데이터를 저장합니다 (예: Vercel을 통한 Redis).
- 영속화 계층 — 각 채팅에 어떤 스트림 ID가 활성인지 추적합니다 (예: 데이터베이스).
구현
1. 클라이언트측: 스트림 재개 활성화
useChat 훅의 resume 옵션으로 스트림 재개를 활성화합니다. resume이 true이면 훅은 마운트 시 채팅의 활성 스트림에 자동으로 재연결을 시도합니다.
resume을 활성화하면 useChat 훅은 마운트 시 활성 스트림을 확인하고 재개하기 위해 /api/chat/[id]/stream에 GET 요청을 보냅니다.
2. POST 핸들러 생성
POST 핸들러는 새 메시지를 받아 streamText로 응답을 생성합니다. consumeSseStream 콜백은 고유 ID로 재개 가능한 스트림을 만들고 resumable-stream 패키지를 통해 Redis에 저장합니다.
3. GET 핸들러 구현
/api/chat/[id]/stream에 GET 핸들러를 만듭니다:
- 라우트 파라미터에서 채팅 ID를 읽습니다.
- 채팅 데이터를 로드해 활성 스트림이 있는지 확인합니다.
- 활성 스트림이 없으면 204 (No Content)를 반환합니다.
- 활성 스트림을 찾으면 기존 스트림을 재개합니다.
동작 방식
요청 라이프사이클
- 스트림 생성: 새 메시지를 보내면 POST 핸들러가
streamText로 응답을 생성합니다.consumeSseStream콜백이 고유 ID의 재개 가능한 스트림을 만들어resumable-stream패키지로 Redis에 저장합니다. - 스트림 추적: 영속화 계층이
activeStreamId를 채팅 데이터에 저장합니다. - 클라이언트 재연결: 클라이언트가 재연결(페이지 리로드)하면
resume옵션이/api/chat/[id]/stream에 GET 요청을 트리거합니다. - 스트림 복구: GET 핸들러가
activeStreamId를 확인하고resumeExistingStream으로 재연결합니다. 활성 스트림이 없으면 204 (No Content)를 반환합니다. - 완료 정리: 스트림이 끝나면
onFinish콜백이activeStreamId를 null로 설정해 정리합니다.
resume 엔드포인트 커스터마이즈
기본적으로 useChat 훅은 재개 시 /api/chat/[id]/stream에 GET 요청을 보냅니다. DefaultChatTransport의 prepareReconnectToStreamRequest 옵션으로 이 엔드포인트, 자격증명, 헤더를 커스터마이즈할 수 있습니다.
활성 재개 가능 스트림 중지
클라이언트측: 현재 어시스턴트 메시지 전송
클라이언트는 현재 어시스턴트 메시지와 activeStreamId를 중지 엔드포인트로 보낸 뒤 chat.stop()을 호출합니다.
서버측: 활성 작업 중지와 스트림 정리
서버는 받은 activeStreamId와 현재 활성 스트림 id를 비교해 일치할 때만 중지 작업을 수행합니다.
내비게이션과 중지 분리
라우트 정리 코드에서 중지 엔드포인트를 호출하지 마세요. 라우트 정리는 연결 끊김이지 명시적 중지가 아닙니다. 사용자가 페이지를 새로고침하거나 내비게이트해도 활성 스트림은 재개 가능해야 합니다. 중지 엔드포인트는 중지 버튼을 누르는 것 같은 명시적 사용자 동작에만 호출하세요.
중요 고려사항
- 스트림 만료: Redis의 스트림은 설정된 시간(
resumable-stream패키지에서 구성) 후 만료됩니다. - 여러 클라이언트: 여러 클라이언트가 동시에 같은 스트림에 연결할 수 있습니다.
- 오류 처리: 활성 스트림이 없으면 GET 핸들러가 204 (No Content) 상태 코드를 반환합니다.