본문으로 건너뛰기

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

데이터가 자체 데이터베이스(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입니다. 생성된 파일을 유지하거나 샌드박스 파일을 재구축할 때는 artifactsblobs를 추가합니다.

일부만 직접 관리할 수도 있습니다. 데이터베이스에 messagesruns를 두고 나머지는 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가 필요합니다.
  • 스토어 참조: 모든 시그니처와 불변식, 그리고 레코드 간의 관계를 설명합니다.
  • 제어: 서로 다른 시스템의 스토어를 조합합니다.
  • 마이그레이션: 스키마를 누가 소유하는지 설명합니다.