Back

기억하는 AI 에이전트 만들기: 프로덕션 LLM 앱을 위한 메모리 아키텍처 완벽 가이드

AI 에이전트가 전부 까먹었어요.

사용자가 20분 동안 자기 코드베이스 구조, 배포 환경, 팀 코딩 스타일을 꼼꼼히 설명했어요. 그리고 후속 질문을 하나 던졌는데... 에이전트가 처음 보는 사람한테 하듯 답변하고 있어요. 컨텍스트 윈도우가 터진 거예요. 47번째 메시지 이전 내용? 전부 증발. 사용자는 처음부터 다시 설명해야 하죠.

장난감 수준의 데모 말고 LLM으로 진짜 뭔가 만들어본 적이 있다면, 이 벽은 익숙할 거예요. 컨텍스트 윈도우는 메모리가 아니거든요. 128K 토큰 윈도우가 커 보여도 대규모 코드베이스 한 번 스캔하면 순식간에 차버려요. 1M 토큰이면 무한해 보이죠? 근데 매 요청마다 꽉 채우면 API 비용이 장난 아니에요. 거기다 아무리 큰 윈도우라도 세션 닫으면 끝. 에이전트는 깔끔하게 기억상실에 걸리죠.

2026년 프로덕션 AI 앱의 핵심 과제가 바로 이거예요: 진짜 기억하는 에이전트, 어떻게 만들지?

"다음 5개 메시지까지만 기억해" 수준이 아니에요. 세션을 넘어서 기억하고, 3주 전 사용자 취향을 기억하고, 지난 화요일 데이터베이스 마이그레이션이 실패했고 임시 해결책이 아직 적용 중이라는 걸 기억하는 거예요. 인간 동료처럼요.

이 글에서는 프로덕션 팀들이 실제로 쓰는 메모리 아키텍처 패턴을 다뤄볼게요. 단순한 슬라이딩 윈도우부터 계층적 메모리 시스템, 그래프 기반 지식 저장소까지 전부요. 실제 구현 코드를 까보면서 주요 프레임워크(Mem0, LangChain Memory, Letta)를 비교하고, 각 패턴이 어디서 한계에 부딪히는지도 같이 살펴봅시다.

컨텍스트 윈도우가 메모리가 아닌 이유

해결책으로 들어가기 전에, 문제를 정확히 이해할 필요가 있어요.

컨텍스트 윈도우의 착각

모든 LLM에는 컨텍스트 윈도우가 있어요. 한 번의 요청에서 처리할 수 있는 최대 토큰 수예요. 2026년에는 이게 상당히 커졌어요:

모델컨텍스트 윈도우대략적인 비용 (입력)
GPT-4.11M 토큰~$2.00/M 토큰
Claude Opus 4200K 토큰~$5.00/M 토큰
Gemini 2.5 Pro1M 토큰~$1.25/M 토큰
Llama 4 Scout10M 토큰셀프 호스팅

"그냥 윈도우 키우면 되지 않나?" 싶겠지만, 그게 안 되는 이유가 있어요:

1. 비용이 선형으로 (또는 그 이상) 뛰어요. 실제 필요한 맥락이 2K 토큰뿐인데 매번 500K 토큰을 때려넣으면 돈을 태우는 거나 마찬가지예요. 2/M토큰기준으로500K짜리요청하나에2/M 토큰 기준으로 500K짜리 요청 하나에 1이거든요. 하루 1,000건이면 일일 $1,000인데, 99%는 쓸데없는 맥락이에요.

2. 노이즈가 많으면 성능이 깨져요. 연구들이 일관되게 보여주는 게, 관련 없는 맥락이 쌓일수록 LLM 성능이 떨어진다는 거예요. "건초 더미에서 바늘 찾기" 문제가 진짜 있어요. 맥락 많다고 답이 좋아지는 거 아니거든요.

3. 맥락이 길면 레이턴시가 커져요. 첫 토큰 생성 시간(TTFT)이 입력 길이에 비례하거든요. 500K 토큰 입력은 5K보다 체감될 정도로 느려서, 채팅 앱의 실시간 느낌이 확 죽어요.

4. 세션 간 지속성이 없어요. 컨텍스트 윈도우는 일시적이에요. 탭 닫으면 전부 사라지고, 외부 저장소 없이는 세션 간에 정보를 넘길 방법 자체가 없어요.

5. 선택적으로 잊는 게 안 돼요. 사람은 모든 걸 기억하지 않잖아요. 중요한 것만 기억하죠. 근데 컨텍스트 윈도우에는 중요도 개념이 아예 없어요. FIFO로 오래된 토큰부터 버리는데, 핵심 스펙이든 잡담이든 구분 없이 날려버리거든요.

진짜 메모리는 이렇게 생겼어요

사람의 기억은 단일 버퍼가 아니에요. 계층 구조예요:

  • 작업 기억 (즉각적 맥락): 지금 바로 생각하고 있는 내용. 용량은 작고 접근은 빠르고.
  • 단기 기억 (최근 사건): 최근 몇 분~몇 시간 동안의 일. 중간 용량, 중간 접근 속도.
  • 장기 기억 (지속적 지식): 시간이 지나도 남는 사실, 기술, 경험. 대용량, 검색에 시간이 걸리고.

효과적인 AI 에이전트 메모리는 이 구조를 따라가요. 하나씩 만들어봅시다.

패턴 1: 슬라이딩 윈도우 + 스마트 잘라내기

가장 단순한 메모리 패턴이에요. 기본 챗봇에는 충분한 경우가 많고요.

동작 원리

최근 N개 메시지를 컨텍스트 윈도우에 유지해요. 대화가 한도를 넘으면 가장 오래된 메시지부터 잘라내요.

interface Message { role: 'user' | 'assistant' | 'system'; content: string; timestamp: number; tokenCount: number; } class SlidingWindowMemory { private messages: Message[] = []; private maxTokens: number; private systemPrompt: Message; constructor(maxTokens: number, systemPrompt: string) { this.maxTokens = maxTokens; this.systemPrompt = { role: 'system', content: systemPrompt, timestamp: Date.now(), tokenCount: this.estimateTokens(systemPrompt), }; } addMessage(role: 'user' | 'assistant', content: string): void { this.messages.push({ role, content, timestamp: Date.now(), tokenCount: this.estimateTokens(content), }); this.trim(); } getContext(): Message[] { return [this.systemPrompt, ...this.messages]; } private trim(): void { let totalTokens = this.systemPrompt.tokenCount + this.messages.reduce((sum, m) => sum + m.tokenCount, 0); while (totalTokens > this.maxTokens && this.messages.length > 2) { const removed = this.messages.shift()!; totalTokens -= removed.tokenCount; } } private estimateTokens(text: string): number { return Math.ceil(text.length / 4); } }

이럴 때 쓰세요

  • 과거 맥락이 크게 중요하지 않은 단순 Q&A 챗봇
  • 짧고 집중된 대화가 이루어지는 고객 지원 봇
  • 프로토타이핑과 MVP 단계

한계점

  • 중요한 초반 맥락이 잘려나가요. 사용자가 첫 메시지에서 요구사항을 설명했다면, 그게 가장 먼저 사라져요.
  • 세션 간 지속성이 없어요. 새 세션마다 백지에서 시작해요.
  • 뭘 남길지 판단하지 않아요. 의미 없는 대화도 핵심 스펙도 동일하게 취급돼요.

패턴 2: 대화 요약

지능적 메모리를 향한 첫걸음이에요. 오래된 맥락을 버리지 않고 압축하는 거죠.

동작 원리

대화가 너무 길어지면 오래된 부분을 요약하고, 그 요약으로 교체해요. 에이전트는 [시스템 프롬프트] + [이전 대화 요약] + [최근 메시지]로 동작해요.

class SummarizingMemory { private messages: Message[] = []; private summary: string = ''; private maxTokens: number; private recentWindowSize: number; private llm: LLMClient; constructor(maxTokens: number, recentWindowSize: number, llm: LLMClient) { this.maxTokens = maxTokens; this.recentWindowSize = recentWindowSize; this.llm = llm; } async addMessage(role: 'user' | 'assistant', content: string): Promise<void> { this.messages.push({ role, content, timestamp: Date.now(), tokenCount: this.estimateTokens(content), }); if (this.shouldSummarize()) { await this.compactHistory(); } } private async compactHistory(): Promise<void> { const messagesToSummarize = this.messages.slice( 0, this.messages.length - this.recentWindowSize ); if (messagesToSummarize.length === 0) return; const conversationText = messagesToSummarize .map(m => `${m.role}: ${m.content}`) .join('\n'); const existingSummary = this.summary ? `기존 요약:\n${this.summary}\n\n` : ''; this.summary = await this.llm.complete({ prompt: `${existingSummary}통합할 새 대화:\n${conversationText}\n\n다음을 보존하는 종합적인 요약을 만들어주세요:\n1. 핵심 결정사항과 결론\n2. 사용자 선호사항과 요구사항\n3. 언급된 기술 사양\n4. 할 일 항목과 대기 중인 작업\n5. 향후 참고에 중요한 맥락`, maxTokens: 500, }); this.messages = this.messages.slice(-this.recentWindowSize); } private shouldSummarize(): boolean { const totalTokens = this.messages.reduce((sum, m) => sum + m.tokenCount, 0); return totalTokens > this.maxTokens * 0.8; } private estimateTokens(text: string): number { return Math.ceil(text.length / 4); } }

점진적 요약 기법

하나의 평면적 요약 대신, 계층적으로 요약하면 효과가 훨씬 좋아요:

class HierarchicalSummarizingMemory { private detailedSummary: string = ''; // 최근 ~30개 메시지 요약 private broadSummary: string = ''; // 그 이전 전체 요약 private recentMessages: Message[] = []; // 최근 ~10개 메시지 async compactHistory(): Promise<void> { // 1단계: 기존 상세 요약을 광범위 요약에 병합 if (this.detailedSummary) { this.broadSummary = await this.llm.complete({ prompt: `기존 상위 요약:\n${this.broadSummary}\n\n통합할 상세 요약:\n${this.detailedSummary}\n\n가장 중요한 사실, 결정, 사용자 선호만 보존하는 상위 요약을 만들어주세요. 최대 200단어.`, maxTokens: 300, }); } // 2단계: 넘치는 최근 메시지를 상세 요약으로 압축 const overflow = this.recentMessages.slice(0, -10); this.detailedSummary = await this.llm.complete({ prompt: `대화 구간:\n${overflow.map(m => `${m.role}: ${m.content}`).join('\n')}\n\n구체적인 기술 세부사항, 언급된 코드 스니펫, 정확한 요구사항을 보존하는 상세 요약을 만들어주세요. 최대 500단어.`, maxTokens: 600, }); this.recentMessages = this.recentMessages.slice(-10); } }

이럴 때 쓰세요

  • 긴 대화가 오가는 채팅 앱
  • 복잡한 다단계 대화가 이루어지는 고객 지원
  • 프로젝트 맥락이 필요한 코딩 어시스턴트

한계점

  • 요약하면 디테일이 빠져요. 요약문에는 "사용자가 REST API를 원한다"고 되어 있지만, HATEOAS 호환 응답에 ETag 헤더를 달라고 한 건 사라져요.
  • 누적 요약 오류. 요약의 요약의 요약을 만들면 정보 손실이 누적돼요. 5번 압축하고 나면 원래 뉘앙스는 없어져요.
  • 요약 비용. 압축할 때마다 LLM 호출이 필요해서 레이턴시와 비용이 추가돼요.

패턴 3: 엔티티 및 사실 추출

대화 전체를 요약하는 대신, 구조화된 사실을 추출해서 저장하는 방식이에요.

동작 원리

상호작용 후 핵심 엔티티와 사실을 영속 저장소에 추출해요. 응답 전에는 현재 질문과 관련된 사실을 검색해와요.

interface Fact { id: string; subject: string; predicate: string; object: string; confidence: number; source: string; timestamp: number; supersedes?: string; } class EntityMemory { private facts: Map<string, Fact[]> = new Map(); private llm: LLMClient; private vectorStore: VectorStore; async extractFacts(messages: Message[]): Promise<Fact[]> { const conversation = messages .map(m => `${m.role}: ${m.content}`) .join('\n'); const response = await this.llm.complete({ prompt: `이 대화에서 핵심 사실을 구조화된 데이터로 추출하세요. 대화: ${conversation} JSON 배열로 반환: [{ "subject": "엔티티명", "predicate": "관계 또는 속성", "object": "값 또는 관련 엔티티", "confidence": 0.0-1.0 }] 집중할 항목: - 사용자 선호사항과 요구사항 - 기술적 결정사항 - 프로젝트 사양 - 마감일과 제약조건 - 언급된 인물과 역할`, responseFormat: 'json', }); return JSON.parse(response).map((f: any) => ({ ...f, id: crypto.randomUUID(), source: 'current_session', timestamp: Date.now(), })); } async recallRelevant(query: string, limit: number = 20): Promise<Fact[]> { const results = await this.vectorStore.search(query, limit); return results .map(r => r.metadata as Fact) .filter(f => !f.supersedes) .sort((a, b) => b.confidence - a.confidence); } }

이럴 때 쓰세요

  • 시간이 지나면서 사용자에 대해 학습해야 하는 개인 AI 어시스턴트
  • 여러 미팅에 걸쳐 결정사항을 추적하는 프로젝트 관리 봇
  • 대화 흐름보다 특정 사실이 중요한 모든 앱

한계점

  • 추출이 완벽하지 않아요. LLM이 뉘앙스를 놓치거나 얘기하지 않은 사실을 만들어내기도 해요.
  • 모순 처리가 어려워요. "마감일이 금요일이에요" 다음에 "아, 월요일로 미루죠"가 오면 시스템이 충돌을 감지하고 해결해야 해요.
  • 사실 진부화. 명시적인 만료 없이는 오래된 사실이 맥락을 오염시켜요.

패턴 4: 계층적 메모리 아키텍처

프로덕션 시스템이 실제로 쓰는 방식이에요. 여러 패턴을 결합해서 인간의 기억 구조를 모방하는 계층 시스템을 만들어요.

3계층 모델

┌─────────────────────────────────────────────┐
│          1계층: 작업 기억 (Working)           │
│  (현재 컨텍스트 윈도우, 최근 ~10개 메시지)     │
│  접근: 즉시 | 용량: 소                        │
├─────────────────────────────────────────────┤
│          2계층: 단기 기억 (Short-Term)        │
│  (세션 요약, 최근 추출 사실)                   │
│  접근: 빠른 검색 | 용량: 중                    │
├─────────────────────────────────────────────┤
│          3계층: 장기 기억 (Long-Term)         │
│  (지식 그래프, 사용자 프로필, 히스토리)         │
│  접근: 시맨틱 검색 | 용량: 대                  │
└─────────────────────────────────────────────┘

구현

class HierarchicalMemory { private workingMemory: Message[] = []; private shortTermMemory: ShortTermStore; private longTermMemory: LongTermStore; private llm: LLMClient; async processMessage(message: Message): Promise<void> { // 1. 작업 기억에 추가 this.workingMemory.push(message); // 2. 5회마다 단기 기억으로 사실 추출 if (this.workingMemory.length % 5 === 0) { const recentFacts = await this.extractFacts( this.workingMemory.slice(-5) ); await this.shortTermMemory.store(recentFacts); } // 3. 20회마다 중요 사실을 장기 기억으로 승격 if (this.workingMemory.length % 20 === 0) { await this.consolidate(); } // 4. 작업 기억 초과 시 압축 if (this.getWorkingMemoryTokens() > 8000) { await this.compactWorkingMemory(); } } async buildContext(query: string): Promise<ContextBundle> { // 세 계층 모두에서 검색 const [shortTermResults, longTermResults] = await Promise.all([ this.shortTermMemory.search(query, 10), this.longTermMemory.search(query, 15), ]); const allFacts = this.deduplicateAndRank([ ...shortTermResults, ...longTermResults, ]); return { systemContext: this.buildSystemContext(allFacts), workingMemory: this.workingMemory.slice(-10), relevantFacts: allFacts.slice(0, 20), tokenBudget: { system: 2000, facts: 3000, working: 8000, response: 4000, }, }; } private async consolidate(): Promise<void> { const shortTermFacts = await this.shortTermMemory.getAll(); const assessment = await this.llm.complete({ prompt: `이 사실들을 검토해서 장기 저장 가치가 있는지 판단하세요. 사실: ${shortTermFacts.map(f => `- ${f.subject} ${f.predicate}: ${f.object}`).join('\n')} 각 사실에 대해: - KEEP: 향후 상호작용에 중요 (사용자 선호, 핵심 결정, 프로젝트 사양) - DISCARD: 일시적이거나 대화적 (인사, 확인, 일시적 상태) - MERGE: 다른 사실과 병합 가능 JSON 배열로 반환: [{id, action, mergeWith?}]`, responseFormat: 'json', }); // 승격 처리... } }

이럴 때 쓰세요

  • 여러 세션에 걸친 상호작용이 있는 프로덕션 AI 어시스턴트
  • 몇 주에 걸쳐 프로젝트 맥락을 기억해야 하는 기업용 코파일럿
  • 장기적 사용자 개인화가 중요한 앱

패턴 5: 그래프 기반 메모리 (GraphRAG)

에이전트 메모리의 최선두. 사실을 평면 텍스트로 저장하는 대신, 관계의 그래프로 표현해요.

그래프가 벡터보다 나은 이유

벡터 유사도 검색(전통적 RAG의 핵심)에는 근본적 한계가 있어요. 비슷하게 들리는 건 찾지만, 구조적으로 연결된 건 놓쳐요.

예시: "Alice가 결제팀을 관리한다"와 "결제팀이 체크아웃 마이크로서비스를 소유한다"는 의미적으로 유사하지 않아요. 하지만 그래프에서는 Alice → 관리 → 결제팀 → 소유 → 체크아웃 서비스로 순회할 수 있어요. "체크아웃 버그에 대해 누구와 얘기해야 하나요?"라는 질문에 그래프는 "Alice"라고 답할 수 있지만, 벡터 스토어는 못 해요.

class GraphMemory { private graph: GraphDatabase; async addKnowledge( subject: string, predicate: string, object: string, metadata: Record<string, any> ): Promise<void> { await this.graph.query(` MERGE (s:Entity {name: $subject}) MERGE (o:Entity {name: $object}) MERGE (s)-[r:${predicate.toUpperCase().replace(/\s/g, '_')}]->(o) SET r += $metadata, r.updatedAt = timestamp() `, { subject, object, metadata }); } async query(question: string): Promise<GraphResult[]> { const entities = await this.extractEntities(question); const subgraph = await this.graph.query(` MATCH (e:Entity)-[r*1..3]-(connected:Entity) WHERE e.name IN $entities RETURN e, r, connected LIMIT 50 `, { entities }); return this.formatSubgraph(subgraph); } }

하이브리드 접근: 벡터 + 그래프

가장 효과적인 프로덕션 시스템은 둘 다 결합해요:

class HybridMemory { private vectorStore: VectorStore; // 시맨틱 유사성 private graphStore: GraphMemory; // 구조적 관계 async recall(query: string): Promise<MemoryResult> { const [vectorResults, graphResults] = await Promise.all([ this.vectorStore.search(query, 10), this.graphStore.query(query), ]); const merged = this.mergeResults(vectorResults, graphResults); return { memories: merged }; } }

프레임워크 비교: Mem0 vs LangChain Memory vs Letta

LLM 앱용 대표 메모리 프레임워크 세 가지를 비교해볼게요.

Mem0

AI 앱을 위한 매니지드 메모리 레이어예요.

import { MemoryClient } from 'mem0ai'; const memory = new MemoryClient({ apiKey: process.env.MEM0_API_KEY }); // 대화에서 메모리 추가 await memory.add( "I prefer Python over JavaScript for backend work", { user_id: "alice", metadata: { category: "preferences" } } ); // 메모리 검색 const results = await memory.search( "What programming language does Alice prefer?", { user_id: "alice" } );

장점: API가 직관적이고, 인프라 설정 필요 없고, 세션 간 메모리가 기본이고, 셀프 호스팅도 돼요. 멀티 스토어(KV + 벡터 + 그래프 레이어)로 구성되어 있어서 꽤 유연하죠.

단점: 메모리 표현 제어가 제한적이고, 랭킹 알고리즘이 블랙박스에요. 그래프 레이어는 벡터 스토어에 비해 아직 검증이 덜 됐어요.

LangChain Memory

다양한 메모리 구현체를 기본 제공해요.

import { BufferWindowMemory } from 'langchain/memory'; import { ConversationSummaryMemory } from 'langchain/memory'; import { VectorStoreRetrieverMemory } from 'langchain/memory'; import { CombinedMemory } from 'langchain/memory'; // 여러 메모리 타입을 조합 가능 const combinedMemory = new CombinedMemory({ memories: [bufferMemory, summaryMemory, vectorMemory], });

장점: 유연성이 최고이고, LangChain 생태계와 깊이 연동되고, 커뮤니티 백엔드(Redis, PostgreSQL 등)가 풍부하고, 오픈소스예요.

단점: 설정과 인프라 결정이 많이 필요하고, 단순한 용도에는 과한 설계가 될 수 있어요.

Letta (구 MemGPT)

근본적으로 다른 접근인데, 메모리 관리를 운영체제 문제로 봐요.

import { Letta } from 'letta'; const agent = await client.createAgent({ name: 'project-assistant', memory: { coreMemory: { persona: 'You are a senior software engineer...', human: '', // 대화에서 자동으로 채워짐 }, archivalMemory: true, recallMemory: true, }, tools: ['archival_memory_insert', 'archival_memory_search', 'core_memory_replace', 'core_memory_append'], });

장점: 자기 관리형 메모리이고, OS에서 영감 받은 아키텍처(코어/아카이벌/리콜)이고, 기본 영속성이에요.

단점: 메모리 관리에 추가 LLM 호출이 필요하고(비용/레이턴시), 이 접근 방식의 생태계가 아직 젊어요.

프레임워크 의사결정 매트릭스

기준Mem0LangChain MemoryLetta
설정 복잡도⭐ 낮음⭐⭐⭐ 높음⭐⭐ 보통
유연성⭐⭐ 보통⭐⭐⭐ 높음⭐⭐ 보통
세션 간 메모리✅ 기본 지원⚙️ 설정 필요✅ 기본 지원
자기 관리형
셀프 호스팅
프로덕션 준비도⭐⭐⭐⭐⭐⭐⭐⭐
적합한 용도빠른 통합커스텀 아키텍처자율 에이전트

프로덕션 패턴과 안티패턴

패턴: 메모리 인식 프롬프트 엔지니어링

가장 큰 영향을 주는 최적화는 메모리 시스템 자체가 아니라, 검색된 메모리를 모델에 어떻게 보여주느냐인 경우가 많아요.

// ❌ 안 좋은 예: 모든 메모리를 flat하게 덤프 const badPrompt = ` 알고 있는 것들: ${memories.map(m => m.text).join('\n')} 사용자: ${query} `; // ✅ 좋은 예: 구조화, 우선순위화, 최신성 표시 const goodPrompt = ` ## 이 사용자에 대해 알고 있는 것 ${highConfidenceMemories.map(m => `- ${m.text} (마지막 확인: ${formatRelativeTime(m.updatedAt)})` ).join('\n')} ## 관련 프로젝트 맥락 ${projectMemories.map(m => `- ${m.text}`).join('\n')} ## 오래됐을 수 있는 정보 (사용 전 확인 필요) ${staleMemories.map(m => `- ${m.text} (${formatDate(m.createdAt)}, 변경됐을 수 있음)` ).join('\n')} `;

안티패턴: 잊기 없는 메모리

기억하는 것만큼 중요한 게, 언제 잊을지 아는 거예요.

class MemoryManager { // 감쇠 구현 — 검색되지 않는 메모리는 점점 희미해짐 async applyDecay(): Promise<void> { const allMemories = await this.store.getAll(); for (const memory of allMemories) { const daysSinceAccess = (Date.now() - memory.lastAccessedAt) / (86400000); const decayFactor = Math.exp(-0.01 * daysSinceAccess); const newConfidence = memory.confidence * decayFactor; if (newConfidence < 0.2) { await this.store.archive(memory.id); // 삭제가 아닌 아카이브 } else { await this.store.updateConfidence(memory.id, newConfidence); } } } }

안티패턴: 단순한 용도에 과한 설계

모든 앱이 GraphRAG가 달린 3계층 계층적 메모리 시스템을 필요로 하는 건 아니에요. 이 결정 트리를 따라가보세요:

1. 대화가 20개 메시지 이하?
   → 슬라이딩 윈도우면 충분해요. 여기서 멈추세요.

2. 사용자가 나중에 같은 대화로 돌아와야 하나요?
   → 대화 요약을 추가하세요. 여기서 멈춰도 돼요.

3. 에이전트가 다른 대화에 걸쳐 사실을 기억해야 하나요?
   → 엔티티 추출 + 벡터 스토어를 추가하세요.

4. 에이전트가 엔티티 간 관계를 이해해야 하나요?
   → 그래프 기반 메모리를 추가하세요.

5. 에이전트가 자율적으로 기억 관리를 해야 하나요?
   → Letta의 자기 관리 접근을 고려하세요.

메모리 시스템 벤치마킹

메모리 시스템이 제대로 동작하는지 어떻게 알까요? 이런 지표를 정의하세요:

지표측정 내용목표
Recall@NN개 메시지 후 사실 회상 가능?>90% (N=50)
모순율에이전트가 오래된 정보를 쓰는 빈도?<5%
메모리 레이턴시관련 메모리 검색 시간<200ms
토큰 효율관련 있는 맥락 vs 전체 맥락 비율>60%
세션 간 회상이전 세션의 사실을 기억하나?>80%

마무리

기억하는 AI 에이전트를 만드는 건, 가장 큰 컨텍스트 윈도우를 찾는 게 아니에요. 내 앱에서 정보가 실제로 흘러야 하는 방식에 맞는 메모리 아키텍처를 설계하는 거죠.

실전 정리하면요:

  1. 단순하게 시작하세요. 슬라이딩 윈도우 + 대화 요약이면 80% 케이스는 커버돼요. 첫날부터 오버 엔지니어링 할 필요 없어요.

  2. 사용자가 원할 때 영속성을 붙이세요. "왜 아까 말한 거 까먹었어요?" 라는 불만이 나오는 순간, 엔티티 추출 + 영속 저장소가 필요한 거예요. 가장 빠른 길은 Mem0이에요.

  3. 평면 메모리가 부족하면 구조를 넣으세요. 조직도, 의존 관계 그래프, 시스템 아키텍처처럼 관계를 이해해야 하는 상황이 올 때, 그래프 기반 메모리가 빛을 발해요.

  4. 충분히 복잡해지면 에이전트 자율 관리를 도입하세요. 몇 시간짜리 태스크를 자율으로 돌리는 에이전트라면 Letta 같은 자기 관리 접근이 하드코딩 규칙의 취약함을 피할 수 있어요.

  5. 잊기는 반드시 구현하세요. 감쇠 없는 메모리 시스템은 자산이 아니라 부채예요. 오래된 사실이 없는 사실보다 피해가 더 커요.

도구가 지난 1년 사이 정말 많이 성숙했어요. 예전에는 인프라를 직접 구축해야 했던 게, 이제는 pip install 하나면 끝나거든요. 어려운 건 이제 기술이 아니에요. 내 앱에 딱 맞는 메모리 아키텍처를 설계하는 게 진짜 어려운 거죠.

사용자는 완벽한 메모리 시스템에 고마워하진 않을 거예요. 근데 에이전트가 까먹는 순간, 확실히 알아채요.

AILLMmemoryagentsRAGLangChainproductionarchitecture

관련 도구 둘러보기

Pockit의 무료 개발자 도구를 사용해 보세요