본문으로 건너뛰기

나만의 영속성 어댑터 만들기

데이터가 자체 데이터베이스(Postgres와 Prisma, SQLite 파일, D1, Mongo 뒤에 있음)에 저장되어 있고 채팅 기록만을 위해 별도 서비스를 추가하고 싶지 않을 수 있습니다. 그럴 필요는 없습니다. 어댑터는 스토어 함수로 구성된 일반 객체입니다. 코어는 테이블을 전혀 확인하지 않으므로 스키마는 그대로 유지됩니다.

작동하는 가장 작은 어댑터​

messages 스토어 하나면 withPersistence에 충분합니다. 전체 코드는 다음과 같습니다.

import {
defineAIPersistence,
defineMessageStore,
} from '@tanstack/ai-persistence'
import { db } from './db'

export const persistence = defineAIPersistence({
stores: {
messages: defineMessageStore({
// Return [] for a thread that was never saved, never null.
loadThread: (threadId) => db.threads.messages(threadId),
// The full transcript, not a delta. Overwrite what you had.
saveThread: (threadId, messages) => db.threads.save(threadId, messages),
}),
},
})

미들웨어에 전달하면 완료됩니다.

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

export const stream = chat({
adapter: openaiText('gpt-5.5'),
messages: [{ role: 'user', content: 'hi' }],
threadId: 'support-chat',
middleware: [withPersistence(persistence)],
})

이미 테이블이 있나요? 위 내용은 새 테이블을 전제로 하지 않습니다. 열 이름은 원하는 대로 지정하고 네이티브 타입(jsonb, timestamptz)을 사용한 다음 스토어 함수 내부에서 변환하면 됩니다. user_id나 감사 타임스탬프 같은 추가 열도 nullable이거나 기본값이 지정되어 있다면 괜찮습니다. 이러한 스토어는 알지 못하는 열에 절대 접근하지 않기 때문입니다. 스토어마다 define*Store 헬퍼가 하나씩 있으며, 각 헬퍼가 객체의 타입을 인라인으로 검사하므로 직접 타입을 지정할 필요가 없습니다.

어떤 스토어가 필요한가요?​

각 스토어는 하나의 기능을 활성화합니다. 해당 열을 찾아 체크 표시가 된 행을 구현합니다.

스토어대화 기록 저장다시 로드한 후 실행 재참여영속적 승인앱 키/값생성 실행 영속화생성된 파일 유지샌드박스 파일 재구축
messages✅✅✅✅❌❌✅
runs❌✅✅❌❌❌❌
interrupts❌❌✅❌❌❌❌
metadata❌❌❌✅❌❌❌
generationRuns❌❌❌❌✅✅❌
artifacts❌❌❌❌❌✅✅
blobs❌❌❌❌❌✅✅
  • 열은 누적됩니다. 채팅과 샌드박스 파일을 함께 사용하면 messages + artifacts + blobs가 필요합니다. 영속적 승인과 생성된 파일을 함께 사용하면 두 열의 합집합이 필요합니다.
  • 두 쌍은 분리할 수 없습니다. interrupts에는 runs가 필요하고, artifacts에는 blobs가 필요합니다.
  • 생성 실행에는 채팅 스토어가 필요하지 않습니다. 생성 영속성을 참조하세요.
  • 샌드박스 파일에는 체크포인트 스토어도 필요합니다. 해당 스토어는 이 표가 아니라 @tanstack/ai-sandbox에 있습니다. 다시 로드한 후 파일 유지를 참조하세요.

일반적인 프로덕션 구성은 messages + runs + interrupts입니다. 생성된 파일을 유지하거나 샌드박스 파일을 재구축할 때는 artifacts와 blobs를 추가합니다.

일부만 직접 관리할 수도 있습니다. 데이터베이스에 messages와 runs를 두고 나머지는 composePersistence를 사용해 다른 곳에서 가져옵니다.

import { composePersistence, memoryPersistence } from '@tanstack/ai-persistence'
import { messages, runs } from './my-postgres-stores'

export const persistence = composePersistence(memoryPersistence(), {
overrides: { messages, runs },
})

이렇게 하면 두 시스템에 걸친 트랜잭션은 제공되지 않으므로, 양쪽을 변경하는 쓰기는 두 번의 쓰기가 됩니다. 스토어 불변식(멱등적인 생성, 없는 경우에만 삽입)이 재시도를 안전하게 만드는 요소입니다.

에이전트가 작성하게 하기​

@tanstack/ai-persistence에는 Agent Skills가 포함되어 있어 스택에 맞는 레시피로 변환해 줍니다. ORM 설정, 스키마 파일, 데이터베이스 핸들을 기준으로 합니다.

pnpm add @tanstack/ai-persistence
npx @tanstack/intent@latest install

그런 다음 "이 앱에 채팅 영속성을 추가해 줘"라고 요청합니다. Drizzle, Prisma, Cloudflare D1뿐 아니라 그 외의 모든 환경(raw pg, Kysely, SQLite, Mongo, Supabase)에 대한 레시피가 있습니다. 직접 읽고 싶다면 스킬은 node_modules/@tanstack/ai-persistence/skills/ 아래의 일반 Markdown 파일입니다.

적합성 테스트 모음으로 검증하기​

대충 눈으로 확인하지 마세요. 패키지로 제공되는 모든 백엔드가 실행하는 동일한 테스트 모음이 사용자 어댑터용으로도 제공됩니다. 구현한 모든 스토어의 모든 메서드를 테스트하며, 미묘한 오류가 발생하기 쉬운 순서 지정 및 멱등성 규칙도 포함합니다.

import { runPersistenceConformance } from '@tanstack/ai-persistence/testkit'
import { sqlitePersistence } from './sqlite-persistence'

runPersistenceConformance('my sqlite adapter', () =>
sqlitePersistence({ url: ':memory:', migrate: true }),
)

제외한 항목을 선언합니다. 제공하지 않는 스토어는 skip에, 선택 사항인 runs 메서드는 skipMethods에 지정합니다.

import { runPersistenceConformance } from '@tanstack/ai-persistence/testkit'
import { chatOnlyPersistence } from './chat-only'

runPersistenceConformance('chat-only adapter', () => chatOnlyPersistence(), {
skip: ['generationRuns', 'artifacts', 'blobs'],
skipMethods: ['runs.listByThread'],
})

누락된 항목을 선언하지 않으면 추가해야 할 정확한 항목을 명시한 메시지와 함께 실패하므로, 절반만 연결된 어댑터가 통과를 보고할 수 없습니다. 이 테스트가 통과하면 어댑터를 withPersistence에 바로 사용할 수 있으며, 생성 스토어를 사용하면 withGenerationPersistence에도 바로 사용할 수 있습니다.

다음 단계​

  • 채팅 어댑터 만들기: 네 가지 채팅 스토어를 메서드별로 모두 다루는 전체 SQLite 안내입니다.
  • 생성 어댑터 만들기: 생성 실행, 아티팩트 및 blob입니다.
  • 샌드박스 어댑터 만들기: 샌드박스 인스턴스 스토어와 영속적인 샌드박스 실행이 runs에 추가하는 내용입니다. 샌드박스를 실행하는 경우에만 필요합니다.
  • 다시 로드한 후 파일 유지: 이 어댑터를 이식 가능한 스냅샷에 재사용합니다. messages, artifacts, blobs가 필요합니다.
  • 스토어 참조: 모든 시그니처와 불변식, 그리고 레코드 간의 관계를 설명합니다.
  • 제어: 서로 다른 시스템의 스토어를 조합합니다.
  • 마이그레이션: 스키마를 누가 소유하는지 설명합니다.