본문으로 건너뛰기

영속성

사용자가 페이지를 새로 고치면 대화가 사라집니다. 대화가 메모리에만 존재했기 때문입니다. 또는 휴대폰에서 앱을 열면 대화가 전혀 표시되지 않습니다. 영속성은 이 두 문제를 모두 해결하며, 서버의 미들웨어 하나와 클라이언트의 옵션 하나, 두 개의 스니펫으로 구성됩니다.

두 번째로 별개의 문제가 있습니다. 응답이 아직 스트리밍되는 동안 소켓 연결이 끊기는 문제입니다. 이는 별도로 추가할 수 있는 다른 계층인 재개 가능한 스트림으로 해결합니다. 아래의 3단계는 두 기능을 결합하며, 대부분의 앱에서 최종적으로 필요로 하는 방식입니다.

프로바이더의 샌드박스가 사라지면 작업 공간 파일도 사라질 수 있습니다. 동일한 영속성 객체를 새로 고친 후에도 파일 유지와 함께 사용합니다.

설치​

pnpm add @tanstack/ai-persistence

클라이언트 측에는 설치가 필요하지 않습니다. 이미 사용하는 프레임워크 패키지에 (@tanstack/ai-react, -vue, -solid, -svelte, -angular, or @tanstack/ai-client).

작성하기 전에 이 패키지의 에이전트 스킬을 코딩 어시스턴트에 연결한 다음, "이 앱에 채팅 영속성 추가"를 요청합니다.

npx @tanstack/intent@latest install

패키지를 설치한 후 실행합니다. Intent는 node_modules를 검사하므로 이후에 추가한 항목이 있다면 다시 실행해야 합니다.

1. 서버: 대화 저장​

withPersistence는 대화 기록, 실행 상태 및 보류 중인 승인 요청을 자체 스토어에 기록합니다. 여기서 persistence는 어댑터입니다. 약 40줄로 직접 빌드하거나 로컬 개발에서는 memoryPersistence()로 시작합니다.

import {
chat,
chatParamsFromRequest,
toServerSentEventsResponse,
} from '@tanstack/ai'
import { openaiText } from '@tanstack/ai-openai'
import { withPersistence } from '@tanstack/ai-persistence'
import { persistence } from './persistence'

export async function POST(request: Request) {
const params = await chatParamsFromRequest(request)
const stream = chat({
adapter: openaiText('gpt-5.5'),
messages: params.messages,
threadId: params.threadId,
runId: params.runId,
...(params.resume ? { resume: params.resume } : {}),
middleware: [withPersistence(persistence)],
})
return toServerSentEventsResponse(stream)
}

2. 클라이언트: 복원​

두 가지 형식이 있으며, 선택은 기록을 누가 소유하는지에 관한 것입니다.

  • **persistence: true**는 서버가 관리하도록 합니다. 브라우저는 아무것도 캐시하지 않고 마운트 시 서버에 스레드를 요청합니다. 다중 사용자 및 다중 디바이스 앱에 가장 적합합니다.
  • **persistence: <adapter>**는 localStoragePersistence(), sessionStoragePersistence() 또는 indexedDBPersistence()를 사용해 브라우저가 관리하도록 합니다. 서버 스토어가 필요하지 않습니다.
import { fetchServerSentEvents, useChat } from '@tanstack/ai-react'

function Chat() {
const { messages, sendMessage } = useChat({
threadId: 'support-chat',
connection: fetchServerSentEvents('/api/chat'),
persistence: true,
// Or keep the transcript in the browser instead:
// persistence: localStoragePersistence(),
})
return <button onClick={() => sendMessage('hi')}>{messages.length}</button>
}

persistence: true를 사용하면 클라이언트가 읽기 위한 GET 하나가 필요하며, 이것이 3단계입니다. 스토리지 어댑터를 사용하면 완료됩니다. 새로 고치면 대화가 표시됩니다.

3. 응답 중 새로 고침에도 유지​

같은 라우트에 GET을 추가합니다. 두 가지 작업을 수행하며 if가 요청마다 하나를 선택합니다. 아직 스트리밍 중인 실행을 재생하거나 저장된 대화 기록을 반환합니다.

import {
chatParamsFromRequest,
memoryStream,
resumeServerSentEventsResponse,
} from '@tanstack/ai'
import { reconstructChat } from '@tanstack/ai-persistence'
import { persistence } from './persistence'

export function GET(request: Request): Response | Promise<Response> {
const durability = memoryStream(request)
// A run still in flight: the client sent a resume offset, so replay its log.
if (durability.resumeFrom() !== null) {
return resumeServerSentEventsResponse({ adapter: durability })
}
// Otherwise return the stored thread, plus a cursor to any run still generating.
return reconstructChat(persistence, request, {
// WITHOUT this, anyone who guesses a thread id gets the whole transcript.
authorize: async (threadId, req) => ownsThread(req, threadId),
})
}

async function ownsThread(request: Request, threadId: string): Promise<boolean> {
void request
void threadId
return true // replace with your session and ownership check
}

useChat이 두 부분을 모두 처리합니다. 마운트 시 대화 기록을 가져오고, 서버가 아직 생성 중인 실행을 보고하면 해당 실행을 이어 받아 응답을 그 자리에서 완료합니다. 추가로 연결할 것은 없으며, 두 번째 디바이스도 동일한 경로를 따릅니다.

POST도 재개 가능하게 만들려면 동일한 어댑터를 응답에 전달합니다. toServerSentEventsResponse(stream, { durability: { adapter: memoryStream(request) } }).

생성 및 샌드박스에도 동일한 개념 적용​

  • 생성(이미지, 동영상, 음성, 전사): 훅도 persistence 옵션을 사용하며, 값은 불리언만 가능합니다. 이 옵션은 generationRuns 스토어를 기반으로 합니다. 생성 영속성 및 프로바이더 URL이 만료된 후에도 바이트를 보관하는 생성된 파일 유지를 참조합니다.
  • 샌드박스 에이전트: 실행은 탭보다 오래 지속될 수 있으며 다른 호스트가 인계할 수 있습니다. 저장할 항목은 샌드박스 어댑터 빌드에서, 이유는 내구성 있는 실행에서 확인합니다.

어떤 설정이 필요한가요?​

원하는 항목활성화할 항목
새로 고쳐도 대화만 유지스토리지 어댑터를 사용하는 2단계
다른 디바이스 또는 서버 재시작 후에도 같은 대화 사용persistence: true를 사용하는 1단계와 2단계
응답 중 새로 고친 뒤 응답 이어 받기1, 2, 3단계
페이지를 연 상태에서 끊긴 소켓 재개재개 가능한 스트림만 사용
사람의 승인을 위해 일시 중지하고 며칠 후 재개interrupts 스토어를 사용하는 1단계
프로바이더 샌드박스가 사라진 후에도 샌드박스 파일 복원새로 고친 후에도 파일 유지

다음 단계​

  • 채팅 영속성: 내구성 있는 인터럽트를 포함한 서버 미들웨어 전체.
  • 클라이언트 영속성: 모드, 스토리지 백엔드 및 각 경우에 새로 고칠 때 복원되는 항목.
  • 자체 어댑터 빌드: 데이터베이스에 스토어를 구현하고 적합성 테스트 모음으로 검증합니다.
  • 제어: 스토어별 백엔드를 조합합니다.
  • 영속성 작동 방식: 두 계층, 스레드 및 실행 식별자, 기록 소유자, 미들웨어 수명 주기를 설명합니다. 예상과 다른 동작이 있을 때 읽습니다.