본문으로 건너뛰기

사용자 지정 어댑터

pgvector, MongoDB, DynamoDB 또는 호스팅 메모리 API 같은 백엔드를 사용하려 하지만 기본 제공되는 inMemory() / redis() 어댑터가 맞지 않을 수 있습니다. 메모리 어댑터는 recallsave라는 두 메서드를 가진 객체일 뿐이므로, 이 가이드는 간단합니다.

메모리를 처음 살펴보나요? 계약의 정의와 미들웨어의 사용 방법은 개요에서 확인합니다.

계약

// The MemoryAdapter contract, from `@tanstack/ai-memory`:
import type { MemoryAdapter } from '@tanstack/ai-memory'
// The shape of the contract, shown for reference.
import type {
MemoryFact,
MemoryScope,
MemorySnapshot,
MemoryTurn,
RecallResult,
SaveReceipt,
} from '@tanstack/ai-memory'

interface MemoryAdapter {
id: string
recall(scope: MemoryScope, query: string): Promise<RecallResult>
save(scope: MemoryScope, turn: MemoryTurn): Promise<Array<SaveReceipt>>
inspect?(scope: MemoryScope): Promise<MemorySnapshot> // optional (devtools)
listFacts?(scope: MemoryScope): Promise<Array<MemoryFact>> // optional (devtools)
}

미들웨어는 다음 두 가지 규칙에 의존합니다.

  1. recall이 관련성을 결정합니다. 렌더링된 systemPrompt(내용이 없으면 빈 문자열)와 선택적 fragments, tools, toolGuidance를 반환합니다. 어휘, 벡터, 하이브리드 또는 벤더 네이티브 등 랭킹 전략은 전적으로 구현에 달려 있습니다.
  2. save가 추출을 담당합니다. { user, assistant } 턴을 원하는 형태로 변환해 영속화합니다. 실제 쓰기마다 하나의 SaveReceipt를 반환합니다.

스코프 격리는 구현의 책임입니다. 한 scope에 대한 recall은 다른 스코프의 데이터를 절대 노출해서는 안 됩니다.

1단계: 뼈대 작성

import type {
MemoryAdapter,
MemoryScope,
MemoryTurn,
RecallResult,
SaveReceipt,
} from '@tanstack/ai-memory'

// node-postgres' Pool, minimally. In your project, use the real type instead:
// import type { Pool } from 'pg'
type Pool = {
query: (
text: string,
values: Array<unknown>,
) => Promise<{ rows: Array<{ text: string }> }>
}

// Your embedding client. Swap in OpenAI, Cohere, a local model, etc.
type Embed = (text: string) => Promise<Array<number>>

export function pgvectorMemory(options: { pool: Pool; embed: Embed }): MemoryAdapter {
const { pool, embed } = options
return {
id: 'pgvector',

async save(scope: MemoryScope, turn: MemoryTurn): Promise<Array<SaveReceipt>> {
const rows = [
{ role: 'user', text: turn.user },
{ role: 'assistant', text: turn.assistant },
]
for (const row of rows) {
const vector = await embed(row.text)
await pool.query(
`INSERT INTO memory (thread_id, user_id, tenant_id, role, text, embedding)
VALUES ($1, $2, $3, $4, $5, $6)`,
[
scope.threadId,
scope.userId ?? null,
scope.tenantId ?? null,
row.role,
row.text,
JSON.stringify(vector),
],
)
}
return [{ ok: true }]
},

async recall(scope: MemoryScope, query: string): Promise<RecallResult> {
const q = await embed(query)
// Match every isolation dim exactly (including NULL). Omitted tenant/user
// must not match rows written with a tenant/user set.
const { rows } = await pool.query(
`SELECT text, 1 - (embedding <=> $1::vector) AS score
FROM memory
WHERE thread_id = $2
AND user_id IS NOT DISTINCT FROM $3::text
AND tenant_id IS NOT DISTINCT FROM $4::text
ORDER BY score DESC
LIMIT 6`,
[
JSON.stringify(q),
scope.threadId,
scope.userId ?? null,
scope.tenantId ?? null,
],
)
const fragments = rows.map((r) => ({ text: r.text, source: 'pgvector' }))
const systemPrompt = fragments.length
? `Relevant memory:\n${fragments.map((f) => `- ${f.text}`).join('\n')}`
: ''
return { systemPrompt, fragments }
},
}
}

이 형태는 일반화할 수 있습니다. 모든 메서드는 scope를 받고 백엔드별 작업을 수행하며 스코프를 격리합니다. 네이티브 검색이 없는 백엔드에서는 스코프의 레코드를 불러와 직접 순위를 지정합니다.

2단계: 계약 모음 실행

@tanstack/ai-memory/tests/contractrunMemoryAdapterContract를 내보냅니다. 새 어댑터를 반환하는 팩토리를 전달합니다. save 후 recall 왕복, 스코프 격리, 빈 recall, receipt 형태 및 선택적 검사 메서드를 검증합니다.

// ignore: imports the `../src/pgvector` module you wrote in Step 1.
// tests/pgvector.test.ts
import { runMemoryAdapterContract } from '@tanstack/ai-memory/tests/contract'
import { pgvectorMemory } from '../src/pgvector'

runMemoryAdapterContract('pgvectorMemory', async () => {
const pool = makeCleanPool() // truncate between tests for a fresh adapter
return pgvectorMemory({ pool, embed })
})

3단계: memoryMiddleware에 연결

모음의 테스트가 통과하면 이 어댑터를 기본 제공 어댑터와 서로 바꿔 사용할 수 있습니다.

// ignore: imports the `./pgvector` module you wrote in Step 1, and assumes
// `messages` / `scope` from your app.
import { chat } from '@tanstack/ai'
import { openaiText } from '@tanstack/ai-openai'
import { memoryMiddleware } from '@tanstack/ai-memory'
import { pgvectorMemory } from './pgvector'

const memory = pgvectorMemory({ pool, embed })

const stream = chat({
adapter: openaiText('gpt-5.5'),
messages,
middleware: [memoryMiddleware({ adapter: memory, scope })],
})

미들웨어는 어댑터 내부를 검사하지 않습니다. recall/save가 전체 인터페이스입니다.

도구 노출(선택 사항)

recall은 모델에 메모리를 직접 제어할 수 있도록 toolstoolGuidance를 반환할 수 있습니다(hindsight() 어댑터가 retain/recall/reflect 도구를 노출하는 방식입니다). 미들웨어는 이를 실행의 도구에 병합하고 검색된 프롬프트 앞에 지침을 삽입합니다. 어댑터가 도구를 노출하지 않으면 tools: [](또는 생략)를 반환합니다.

주의 사항

  • 스코프를 격리합니다. 스코프를 복합 키로 직렬화한다면 구분 기호를 이스케이프하여 이를 포함하는 threadId/userId/tenantId가 다른 스코프와 충돌하지 않게 합니다.
  • 빈 스코프에서 recall이 예외를 발생시키면 안 됩니다. { systemPrompt: '' }를 반환합니다.
  • 추출은 save에서 수행합니다. 미들웨어가 사실을 파생한다고 기대하지 않습니다. 원시 턴이 전달되므로 원하는 방식으로 저장하거나 요약합니다.

다음 단계

  • 개요: recall/save 계약, 범위 및 턴 흐름 방식
  • 어댑터: 내장 및 벤더 어댑터와 모든 옵션
  • 빠른 시작: memoryMiddleware를 실제 chat() 호출에 연결
  • 운영 메모리: 옵션, 텔레메트리, 개발 도구 이벤트 및 오류