본문으로 건너뛰기
Kreath Archive
TechProjectsBooksAbout
TechProjectsBooksAbout

내비게이션

  • Tech
  • Projects
  • Books
  • About
  • Tags

카테고리

  • AI / ML
  • 웹 개발
  • 프로그래밍
  • 개발 도구

연결

  • GitHub
  • Email
  • RSS
© 2026 Kreath Archive. All rights reserved.Built with Next.js + MDX
홈TechProjectsBooksAbout
//
  1. 홈
  2. 테크
  3. 3장: 장기 메모리 — 벡터 기반 의미 검색
2026년 3월 2일·AI / ML·

3장: 장기 메모리 — 벡터 기반 의미 검색

벡터 데이터베이스에 메모리를 저장하고 임베딩 기반으로 검색하는 장기 메모리 시스템의 설계와 구현 전략을 다룹니다.

16분498자9개 섹션
aillmai-agent
공유
agent-memory3 / 10
12345678910
이전2장: 단기 메모리와 컨텍스트 윈도우 관리다음4장: 에피소딕 메모리 — 경험에서 학습하기

학습 목표

  • 장기 메모리의 역할과 벡터 데이터베이스의 관계를 이해한다
  • 임베딩 기반 의미 검색의 원리와 구현 방법을 익힌다
  • 메모리 인덱싱 전략과 유사도 임계값 설정을 적용할 수 있다
  • Pinecone, Qdrant, pgvector 등 주요 벡터 DB의 특성을 비교할 수 있다

장기 메모리의 필요성

2장에서 다룬 단기 메모리는 세션이 종료되면 사라집니다. 하지만 실제 에이전트 서비스에서는 다음과 같은 요구사항이 있습니다.

  • 사용자가 3일 전에 말한 선호도를 기억해야 합니다
  • 지난주에 해결한 문제의 맥락을 참조해야 합니다
  • 수백 건의 과거 대화에서 현재 질문과 관련된 정보를 찾아야 합니다

이러한 요구를 충족하려면 세션을 넘어 지속되는 장기 메모리(Long-term Memory)가 필요합니다. 그리고 이 장기 메모리의 핵심 인프라가 바로 벡터 데이터베이스(Vector Database)입니다.


임베딩과 벡터 검색의 원리

텍스트를 벡터로 변환

임베딩(Embedding)은 텍스트를 고차원 벡터 공간의 점으로 변환하는 과정입니다. 의미적으로 유사한 텍스트는 벡터 공간에서 서로 가까이 위치합니다.

의미 검색 과정

  1. 메모리를 저장할 때 텍스트를 임베딩하여 벡터로 변환합니다
  2. 벡터와 원본 텍스트, 메타데이터를 벡터 DB에 저장합니다
  3. 검색할 때 쿼리를 동일한 임베딩 모델로 벡터화합니다
  4. 벡터 DB에서 코사인 유사도 등으로 가장 가까운 벡터들을 찾습니다
  5. 해당 벡터에 연결된 원본 메모리를 반환합니다
memory-store.ts
typescript
import { EmbeddingModel } from "./embeddings";
import { VectorDB } from "./vector-db";
 
interface MemoryRecord {
  id: string;
  content: string;
  metadata: {
    userId: string;
    sessionId: string;
    timestamp: number;
    type: "fact" | "preference" | "interaction";
  };
  embedding?: number[];
}
 
class LongTermMemory {
  constructor(
    private embedder: EmbeddingModel,
    private db: VectorDB
  ) {}
 
  async store(content: string, metadata: MemoryRecord["metadata"]): Promise<void> {
    const embedding = await this.embedder.embed(content);
 
    await this.db.upsert({
      id: crypto.randomUUID(),
      content,
      metadata,
      embedding,
    });
  }
 
  async search(
    query: string,
    userId: string,
    topK: number = 5
  ): Promise<MemoryRecord[]> {
    const queryEmbedding = await this.embedder.embed(query);
 
    return this.db.query({
      vector: queryEmbedding,
      filter: { userId },
      topK,
    });
  }
}

메모리 인덱싱 전략

메모리를 효과적으로 저장하고 검색하려면 인덱싱 전략이 중요합니다.

청킹 전략

긴 대화를 그대로 하나의 벡터로 저장하면 검색 정밀도가 떨어집니다. 적절한 단위로 분할(Chunking)하여 저장해야 합니다.

chunking-strategies.ts
typescript
// 전략 1: 대화 턴 단위
function chunkByTurn(messages: Message[]): string[] {
  const chunks: string[] = [];
  for (let i = 0; i < messages.length; i += 2) {
    const userMsg = messages[i];
    const assistantMsg = messages[i + 1];
    if (userMsg && assistantMsg) {
      chunks.push(
        `사용자: ${userMsg.content}\n에이전트: ${assistantMsg.content}`
      );
    }
  }
  return chunks;
}
 
// 전략 2: 주제 단위
function chunkByTopic(messages: Message[]): string[] {
  // 주제 전환 감지 후 그룹화
  // 실제로는 LLM이나 토픽 모델링으로 경계를 판단
  return detectTopicBoundaries(messages).map(
    (group) => group.map((m) => `[${m.role}]: ${m.content}`).join("\n")
  );
}
 
// 전략 3: 추출된 사실 단위
function chunkByFact(messages: Message[]): string[] {
  // LLM을 사용하여 대화에서 개별 사실을 추출
  // "사용자는 TypeScript를 선호한다"
  // "프로젝트 마감일은 4월 15일이다"
  return extractFacts(messages);
}
Tip

Mem0와 같은 프레임워크는 사실 단위 청킹을 자동으로 수행합니다. 대화에서 저장할 가치가 있는 정보를 LLM이 판단하고 추출하여 개별 메모리 항목으로 저장합니다.

메타데이터 설계

벡터 검색만으로는 충분하지 않은 경우가 많습니다. 메타데이터 필터링을 병행하면 검색 정밀도가 크게 향상됩니다.

metadata-schema.ts
typescript
interface MemoryMetadata {
  // 필수 필터링 필드
  userId: string;
  organizationId?: string;
 
  // 시간 관련
  createdAt: number;      // Unix timestamp
  lastAccessedAt: number;
  accessCount: number;
 
  // 분류
  type: "fact" | "preference" | "decision" | "interaction";
  topic: string;           // 주제 태그
  importance: number;      // 0.0 ~ 1.0
 
  // 출처 추적
  sourceSessionId: string;
  sourceMessageIndex: number;
}

유사도 임계값과 관련성 스코어링

벡터 검색은 항상 결과를 반환합니다. 질문과 전혀 무관한 메모리라도 "가장 가까운" 결과로 반환될 수 있습니다. 따라서 유사도 임계값(Similarity Threshold) 설정이 필수입니다.

relevance-scoring.ts
typescript
interface SearchResult {
  content: string;
  similarity: number;  // 0.0 ~ 1.0
  metadata: MemoryMetadata;
}
 
function filterAndRank(
  results: SearchResult[],
  options: {
    minSimilarity: number;   // 최소 유사도 임계값
    recencyWeight: number;   // 시간 가중치
    importanceWeight: number; // 중요도 가중치
  }
): SearchResult[] {
  const now = Date.now();
 
  return results
    // 1. 임계값 이하 제거
    .filter((r) => r.similarity >= options.minSimilarity)
    // 2. 복합 스코어 계산
    .map((r) => {
      const daysSinceCreation =
        (now - r.metadata.createdAt) / (1000 * 60 * 60 * 24);
      const recencyScore = Math.exp(-daysSinceCreation / 30); // 30일 반감기
 
      const compositeScore =
        r.similarity * 0.5 +
        recencyScore * options.recencyWeight +
        r.metadata.importance * options.importanceWeight;
 
      return { ...r, compositeScore };
    })
    // 3. 복합 스코어 기준 정렬
    .sort((a, b) => b.compositeScore - a.compositeScore);
}

시간 가중치의 역할

동일한 유사도를 가진 메모리라도, 최근에 저장된 것이 더 관련성이 높을 가능성이 큽니다. 시간 감쇠(Time Decay) 함수를 적용하면 오래된 메모리의 우선순위를 자연스럽게 낮출 수 있습니다.

Info

유사도 임계값은 도메인과 임베딩 모델에 따라 달라집니다. 일반적으로 코사인 유사도 0.7 이상을 기준으로 시작하되, 실제 데이터로 테스트하며 조정하는 것을 권장합니다.


벡터 데이터베이스 비교

2026년 기준 주요 벡터 데이터베이스의 특성을 비교합니다.

Pinecone

완전 관리형 서비스로, 인프라 관리 없이 벡터 검색을 사용할 수 있습니다. 서버리스 아키텍처를 지원하며 대규모 트래픽에서도 안정적입니다.

pinecone-example.py
python
from pinecone import Pinecone
 
pc = Pinecone(api_key="your-api-key")
index = pc.Index("agent-memory")
 
# 메모리 저장
index.upsert(
    vectors=[
        {
            "id": "mem_001",
            "values": embedding_vector,
            "metadata": {
                "user_id": "user_123",
                "type": "preference",
                "content": "사용자는 TypeScript를 선호합니다",
            },
        }
    ]
)
 
# 메모리 검색
results = index.query(
    vector=query_embedding,
    filter={"user_id": "user_123"},
    top_k=5,
    include_metadata=True,
)

Qdrant

Rust로 작성된 고성능 오픈소스 벡터 DB입니다. 셀프 호스팅이 가능하며, 풍부한 필터링 기능을 제공합니다. 복잡한 메타데이터 필터와 벡터 검색을 동시에 수행하는 데 강점이 있습니다.

pgvector

PostgreSQL의 확장으로, 기존 PostgreSQL 인프라에 벡터 검색 기능을 추가합니다. 별도의 벡터 DB를 운영하지 않아도 되므로 인프라 복잡도를 줄일 수 있습니다.

pgvector-setup.sql
sql
-- pgvector 확장 활성화
CREATE EXTENSION IF NOT EXISTS vector;
 
-- 메모리 테이블 생성
CREATE TABLE agent_memories (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  user_id TEXT NOT NULL,
  content TEXT NOT NULL,
  embedding vector(1536),  -- OpenAI ada-002 차원
  memory_type TEXT NOT NULL,
  importance FLOAT DEFAULT 0.5,
  created_at TIMESTAMPTZ DEFAULT NOW(),
  last_accessed_at TIMESTAMPTZ DEFAULT NOW(),
  access_count INTEGER DEFAULT 0
);
 
-- IVFFlat 인덱스 생성 (대량 데이터 시 검색 가속)
CREATE INDEX ON agent_memories
  USING ivfflat (embedding vector_cosine_ops)
  WITH (lists = 100);
 
-- 의미 검색 쿼리
SELECT content, importance,
       1 - (embedding <=> $1::vector) AS similarity
FROM agent_memories
WHERE user_id = $2
  AND 1 - (embedding <=> $1::vector) > 0.7
ORDER BY similarity DESC
LIMIT 5;

선택 가이드

기준PineconeQdrantpgvector
운영 방식완전 관리형셀프/클라우드기존 PG 활용
초기 도입 난이도낮음중간낮음 (PG 사용 시)
대규모 확장성우수우수보통
비용사용량 기반무료 (셀프)무료 (셀프)
메타데이터 필터링좋음매우 좋음SQL 활용
추천 시나리오빠른 MVP, 서버리스고급 필터링 필요기존 PG 인프라

메모리 생명주기 관리

장기 메모리는 무한히 축적되면 검색 성능이 저하됩니다. 적절한 생명주기 관리가 필요합니다.

메모리 갱신

동일한 주제의 새로운 정보가 입력되면 기존 메모리를 갱신해야 합니다. "사용자가 Python을 선호한다"는 메모리가 있는데 "이제 TypeScript로 전환했다"는 정보가 들어오면, 기존 메모리를 업데이트해야 합니다.

메모리 병합

유사한 내용의 메모리가 여러 개 존재하면 하나로 통합합니다. 이는 검색 시 중복 결과를 줄이고, 저장 공간을 절약합니다.

메모리 만료

오래되고 접근 빈도가 낮은 메모리는 아카이브하거나 삭제합니다. 접근 횟수와 마지막 접근 시간을 기준으로 자동 만료 정책을 설정할 수 있습니다.

memory-lifecycle.ts
typescript
async function cleanupStaleMemories(
  db: VectorDB,
  userId: string,
  maxAgeDays: number = 90,
  minAccessCount: number = 2
): Promise<number> {
  const cutoffDate = Date.now() - maxAgeDays * 24 * 60 * 60 * 1000;
 
  const staleMemories = await db.query({
    filter: {
      userId,
      lastAccessedAt: { $lt: cutoffDate },
      accessCount: { $lt: minAccessCount },
      importance: { $lt: 0.3 },
    },
  });
 
  await db.delete(staleMemories.map((m) => m.id));
  return staleMemories.length;
}

정리

이번 장에서 다룬 장기 메모리의 핵심 내용을 정리합니다.

  • 임베딩 기반 의미 검색은 텍스트를 벡터로 변환하여 의미적 유사성으로 메모리를 찾습니다
  • 청킹 전략은 대화 턴, 주제, 사실 단위 등 다양한 기준으로 적용할 수 있습니다
  • 복합 스코어링은 벡터 유사도에 시간 가중치와 중요도를 결합하여 관련성을 판단합니다
  • 벡터 DB 선택은 운영 방식, 확장성, 기존 인프라를 고려하여 결정합니다
  • 생명주기 관리로 메모리의 갱신, 병합, 만료를 체계적으로 처리합니다

다음 장 미리보기

4장에서는 에피소딕 메모리를 다룹니다. 단순한 사실 저장을 넘어, 에이전트가 과거 경험에서 학습하고 유사한 상황에서 더 나은 의사결정을 내리는 메커니즘을 살펴봅니다.

이 글이 도움이 되셨나요?

관련 주제 더 보기

#ai#llm#ai-agent

관련 글

AI / ML

4장: 에피소딕 메모리 — 경험에서 학습하기

에이전트가 과거 상호작용을 에피소드로 기록하고, 경험 기반 의사결정과 패턴 학습에 활용하는 에피소딕 메모리 시스템을 다룹니다.

2026년 3월 4일·17분
AI / ML

2장: 단기 메모리와 컨텍스트 윈도우 관리

슬라이딩 윈도우, 메시지 요약, 토큰 예산 관리, 중요도 기반 정리 등 에이전트 단기 메모리의 핵심 전략을 코드 예제와 함께 다룹니다.

2026년 2월 28일·16분
AI / ML

5장: 지식 그래프 기반 메모리 — Zep 아키텍처

Zep의 시간 인식 동적 지식 그래프를 중심으로, 엔티티 추출, 관계 생성, 시간적 추론 등 구조화된 메모리의 설계와 장점을 다룹니다.

2026년 3월 6일·16분
이전 글2장: 단기 메모리와 컨텍스트 윈도우 관리
다음 글4장: 에피소딕 메모리 — 경험에서 학습하기

댓글

목차

약 16분 남음
  • 학습 목표
  • 장기 메모리의 필요성
  • 임베딩과 벡터 검색의 원리
    • 텍스트를 벡터로 변환
    • 의미 검색 과정
  • 메모리 인덱싱 전략
    • 청킹 전략
    • 메타데이터 설계
  • 유사도 임계값과 관련성 스코어링
    • 시간 가중치의 역할
  • 벡터 데이터베이스 비교
    • Pinecone
    • Qdrant
    • pgvector
    • 선택 가이드
  • 메모리 생명주기 관리
    • 메모리 갱신
    • 메모리 병합
    • 메모리 만료
  • 정리
  • 다음 장 미리보기