본문으로 건너뛰기

영속성

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

두 번째로 별개의 문제가 있습니다. 응답이 아직 스트리밍되는 동안 소켓 연결이 끊기는 문제입니다. 이는 별도로 추가할 수 있는 다른 계층인 재개 가능한 스트림으로 해결합니다. 아래의 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단계
프로바이더 샌드박스가 사라진 후에도 샌드박스 파일 복원새로 고친 후에도 파일 유지

다음 단계

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