EP 09

페퍼한테 기억을 심는 법


2026년 5월·6 min read·#memory#context#embedding

LLM은 기억이 없다.

각 요청은 독립적이다. 어제 나눈 대화, 아까 저장한 정보 — 아무것도 기본적으로 이어지지 않는다.

그러면 어떻게 페퍼가 우리 가족을 아는 척을 할 수 있나.


레이어 1 — 최근 10턴 직접 주입

가장 기본적인 방법. 최근 대화 10턴을 매 요청마다 통째로 LLM에 넣는다.

const N_HISTORY_TURNS = 10

// DB에서 최근 10개 메시지 가져와서
// 그대로 LLM 메시지 배열에 추가
historyMessages: [
  { role: 'user', content: '아까 말한 그 식당' },
  { role: 'assistant', content: '망원동 OO식당 말씀하시는 거죠?' },
  // ...
]

"아까 말한 거 기억해?" 같은 건 이걸로 해결된다. 근데 10턴 이전은 모른다. 대화가 길어지면 맥락이 잘린다.


레이어 2 — 60개 메시지, 요약해서 주입

그래서 두 번째 레이어를 만들었다.

최근 60개 메시지를 가져온다. 그 중 최근 10개는 레이어 1에서 직접 쓴다. 나머지 50개를 Flash-Lite로 요약해서 따로 주입한다.

const TOTAL_FETCH   = 60
const RECENT_WINDOW = 10

const older = messages.slice(0, -RECENT_WINDOW)  // 최근 10개 제외한 나머지 50개
// → Flash-Lite로 3-5줄 요약
// → "지난 대화 요약: GOOGL 245달러에 매수 이야기 있었음. 은수 학원 일정 논의..."

이 요약이 매 요청마다 컨텍스트에 들어간다. 10턴 이전 내용도 압축돼서 페퍼가 알고 있다.

요약은 매번 새로 만들면 느리고 비싸다. 그래서 캐시를 만들었다.

// in-memory Map — 서버 내에서 0ms로 접근
const summaryMap = new Map<string, CacheEntry>()

// 요청마다: Map 확인 → 있으면 즉시 반환
// 없으면: DB에서 꺼내서 Map 채우기 (cold start 1회)
// 백그라운드: 5분마다 새 요약으로 갱신 (메인 응답 블로킹 없음)

scheduleRefresh()
는 fire-and-forget이다. 요약 갱신이 필요해도 사용자는 기다리지 않는다. 갱신된 요약은 다음 요청부터 적용된다.


레이어 3 — 임베딩으로 Vault 검색

세 번째 레이어는 다르다. 히스토리가 아니라 저장된 정보를 찾는 것.

"GOOGL 평단가 얼마야?" 같은 질문이 오면, Vault에서 관련 정보를 찾아야 한다. 텍스트 검색으로는 한계가 있다. "평단가"라는 단어가 없어도 관련 항목을 찾아야 한다.

임베딩을 쓴다. 질문을 768차원 벡터로 변환하고, Vault 항목들과 코사인 유사도를 비교한다. 0.65 이상 유사한 항목을 최대 5개 가져와서 컨텍스트에 주입한다.

const RECALL_LIMIT = 5
const SIMILARITY_THRESHOLD = 0.65

// 1. 질문 → 768차원 벡터
const queryVector = await embed(query)

// 2. pgvector로 유사도 검색
const results = await db.rpc('vault_semantic_search', {
  query_embedding: queryVector,
  similarity_threshold: SIMILARITY_THRESHOLD,
  match_limit: RECALL_LIMIT,
})
// → 관련 Vault 항목 최대 5개

처음에 "GOOGL 평단가는 저장된 정보가 없어요"라고 나왔을 때 임베딩 모델 버그였다.

text-embedding-004
가 이 API key에서 404를 뱉고 있었다.
.catch(() => null)
로 조용히 실패하고 있어서 한참 못 찾았다.


세 레이어가 쌓이면

결국 페퍼가 응답할 때 이것들이 모두 들어간다.

  • 페퍼의 성격과 가족 정보 (persona.md)
  • 지난 대화 요약 (레이어 2)
  • 최근 10턴 대화 (레이어 1)
  • 관련 Vault 기억 (레이어 3, recall일 때만)
  • 지금 메시지

LLM은 기억이 없지만, 매번 기억을 만들어서 넣어줄 수 있다. 기억이 있는 것처럼 만드는 것 — 이게 지금 페퍼의 메모리 구조다.