초안 DRAFT Agentic Design Patterns - 메모리 관리
TypeScript, LangChain, LangGraph, Vercel 서버리스 플랫폼을 사용하여 과거 상호작용을 기억하고, 세션 간 컨텍스트를 유지하며, 경험으로부터 학습하는 AI 에이전트 구축하기.
멘탈 모델: 메모리 우선 에이전트 구축
에이전트 메모리를 스테로이드를 투여한 웹 애플리케이션의 상태 관리 시스템으로 생각해보세요. React 앱이 상태를 위해 Redux를, 공유 데이터를 위해 Context API를, 지속성을 위해 localStorage를 사용하는 것처럼, AI 에이전트는 작업 메모리(useState와 같은), 에피소드 메모리(세션 스토리지와 같은), 의미적 메모리(데이터베이스와 같은), 절차적 메모리(캐시된 계산과 같은)가 필요합니다. LangGraph는 메모리 오케스트레이터(Redux와 유사) 역할을 하며, Redis 및 벡터 데이터베이스와 같은 외부 저장소는 지속성 계층 역할을 합니다. 핵심 차이점: 에이전트 메모리는 구조화된 데이터와 의미적 이해를 모두 처리해야 하며, 서버리스 제약 내에서 효율적으로 작동해야 합니다.
기본 예제: 대화 버퍼 메모리
1. 간단한 버퍼 메모리 구현
// lib/memory/buffer-memory.ts
import { BufferMemory } from 'langchain/memory';
import { ChatGoogleGenerativeAI } from '@langchain/google-genai';
import { ConversationChain } from 'langchain/chains';
import { map, take } from 'es-toolkit';
export function createBasicMemoryChain() {
const model = new ChatGoogleGenerativeAI({
modelName: 'gemini-2.5-flash',
temperature: 0.7,
});
const memory = new BufferMemory({
returnMessages: true,
memoryKey: 'history',
});
const chain = new ConversationChain({
llm: model,
memory,
verbose: false, // 디버깅을 위해 true로 설정
});
return chain;
}
// app/api/chat-memory/route.ts
import { createBasicMemoryChain } from '@/lib/memory/buffer-memory';
import { NextResponse } from 'next/server';
export const runtime = 'nodejs';
export const maxDuration = 60;
const chain = createBasicMemoryChain();
export async function POST(req: Request) {
const { message } = await req.json();
const result = await chain.call({ input: message });
return NextResponse.json({
response: result.response,
memorySize: chain.memory.chatHistory.messages.length
});
}
세션 동안 모든 메시지를 메모리에 유지하는 BufferMemory를 가진 기본 대화 체인을 생성합니다.
2. 제한된 컨텍스트를 가진 윈도우 메모리
// lib/memory/window-memory.ts
import { BufferWindowMemory } from 'langchain/memory';
import { ChatGoogleGenerativeAI } from '@langchain/google-genai';
import { ConversationChain } from 'langchain/chains';
import { takeRight } from 'es-toolkit';
interface WindowMemoryConfig {
windowSize?: number;
returnMessages?: boolean;
}
export class WindowMemoryManager {
private chain: ConversationChain;
private windowSize: number;
constructor(config: WindowMemoryConfig = {}) {
const model = new ChatGoogleGenerativeAI({
modelName: 'gemini-2.5-flash',
temperature: 0.7,
});
this.windowSize = config.windowSize || 10;
const memory = new BufferWindowMemory({
k: this.windowSize,
returnMessages: config.returnMessages ?? true,
});
this.chain = new ConversationChain({
llm: model,
memory,
});
}
async processMessage(message: string) {
const result = await this.chain.call({ input: message });
// 현재 윈도우 가져오기
const messages = this.chain.memory.chatHistory.messages;
const windowMessages = takeRight(messages, this.windowSize * 2); // 사용자 + AI 메시지
return {
response: result.response,
windowSize: windowMessages.length,
droppedMessages: Math.max(0, messages.length - windowMessages.length)
};
}
}
마지막 K개의 메시지 쌍만 유지하고, 제한을 유지하기 위해 이전 컨텍스트를 자동으로 삭제하는 슬라이딩 윈도우 메모리를 구현합니다.
3. 긴 대화를 위한 요약 메모리
// lib/memory/summary-memory.ts
import { ConversationSummaryMemory } from 'langchain/memory';
import { ChatGoogleGenerativeAI } from '@langchain/google-genai';
import { ConversationChain } from 'langchain/chains';
import { debounce } from 'es-toolkit';
export class SummaryMemoryManager {
private chain: ConversationChain;
private summaryModel: ChatGoogleGenerativeAI;
private updateSummary: Function;
constructor() {
const model = new ChatGoogleGenerativeAI({
modelName: 'gemini-2.5-pro',
temperature: 0.7,
});
this.summaryModel = new ChatGoogleGenerativeAI({
modelName: 'gemini-2.5-flash',
temperature: 0,
});
const memory = new ConversationSummaryMemory({
llm: this.summaryModel,
returnMessages: false,
inputKey: 'input',
outputKey: 'response',
});
this.chain = new ConversationChain({
llm: model,
memory,
});
// 성능을 위한 요약 업데이트 디바운스
this.updateSummary = debounce(
() => this.chain.memory.predictNewSummary(
this.chain.memory.chatHistory.messages,
''
),
5000
);
}
async processWithSummary(message: string) {
const result = await this.chain.call({ input: message });
// 요약 업데이트 트리거 (디바운스됨)
this.updateSummary();
return {
response: result.response,
currentSummary: this.chain.memory.buffer
};
}
}
LLM을 사용하여 대화를 점진적으로 요약하고, 긴 상호작용에서 토큰 사용량을 줄이면서 컨텍스트를 유지합니다.
고급 예제: LangGraph를 사용한 멀티 티어 메모리 시스템
1. LangGraph를 사용한 상태 기반 메모리
// lib/memory/langgraph-memory.ts
import { StateGraph, Annotation } from '@langchain/langgraph';
import { BaseMessage, HumanMessage, AIMessage } from '@langchain/core/messages';
import { ChatGoogleGenerativeAI } from '@langchain/google-genai';
import { MemorySaver } from '@langchain/langgraph';
import { groupBy, orderBy, take } from 'es-toolkit';
// 상태 구조 정의
const StateAnnotation = Annotation.Root({
messages: Annotation<BaseMessage[]>({
reducer: (x, y) => [...x, ...y],
default: () => [],
}),
summary: Annotation<string>({
reducer: (x, y) => y || x,
default: () => '',
}),
userProfile: Annotation<Record<string, any>>({
reducer: (x, y) => ({ ...x, ...y }),
default: () => ({}),
}),
sessionMetadata: Annotation<Record<string, any>>({
reducer: (x, y) => ({ ...x, ...y }),
default: () => ({ startTime: Date.now() }),
}),
});
export function createStatefulMemoryAgent() {
const model = new ChatGoogleGenerativeAI({
modelName: 'gemini-2.5-pro',
temperature: 0.7,
});
const summaryModel = new ChatGoogleGenerativeAI({
modelName: 'gemini-2.5-flash',
temperature: 0,
});
const workflow = new StateGraph(StateAnnotation)
.addNode('processMessage', async (state) => {
// 전체 컨텍스트로 처리
const contextMessages = [
new SystemMessage(`사용자 프로필: ${JSON.stringify(state.userProfile)}`),
new SystemMessage(`과거 요약: ${state.summary}`),
...take(state.messages, 10), // 최근 메시지
];
const response = await model.invoke(contextMessages);
return {
messages: [response],
};
})
.addNode('updateProfile', async (state) => {
// 대화에서 사용자 선호도 추출
const recentMessages = take(state.messages, 5);
const analysis = await summaryModel.invoke([
new SystemMessage('사용자 선호도와 사실을 JSON으로 추출'),
...recentMessages,
]);
try {
const preferences = JSON.parse(analysis.content as string);
return { userProfile: preferences };
} catch {
return {};
}
})
.addNode('summarizeIfNeeded', async (state) => {
// 메시지가 임계값을 초과할 때 요약
if (state.messages.length > 20) {
const oldMessages = state.messages.slice(0, -10);
const summary = await summaryModel.invoke([
new SystemMessage('이 대화를 간결하게 요약하세요'),
...oldMessages,
]);
return {
summary: summary.content as string,
messages: state.messages.slice(-10), // 최근 것만 유지
};
}
return {};
});
// 플로우 정의
workflow.addEdge('__start__', 'processMessage');
workflow.addEdge('processMessage', 'updateProfile');
workflow.addEdge('updateProfile', 'summarizeIfNeeded');
workflow.addEdge('summarizeIfNeeded', '__end__');
// 지속성 추가
const checkpointer = new MemorySaver();
return workflow.compile({ checkpointer });
}
// app/api/stateful-chat/route.ts
import { createStatefulMemoryAgent } from '@/lib/memory/langgraph-memory';
import { HumanMessage } from '@langchain/core/messages';
export const runtime = 'nodejs';
export const maxDuration = 300;
const agent = createStatefulMemoryAgent();
export async function POST(req: Request) {
const { message, threadId } = await req.json();
const result = await agent.invoke(
{
messages: [new HumanMessage(message)],
},
{
configurable: { thread_id: threadId || 'default' },
}
);
return NextResponse.json({
response: result.messages[result.messages.length - 1].content,
profile: result.userProfile,
messageCount: result.messages.length,
});
}
LangGraph를 사용하여 자동 요약, 사용자 프로파일링, 스레드 기반 지속성을 가진 정교한 메모리 시스템을 구현합니다.
2. 의미적 검색을 가진 벡터 메모리
// lib/memory/vector-memory.ts
import { MemoryVectorStore } from 'langchain/vectorstores/memory';
import { GoogleGenerativeAIEmbeddings } from '@langchain/google-genai';
import { VectorStoreRetrieverMemory } from 'langchain/memory';
import { ChatGoogleGenerativeAI } from '@langchain/google-genai';
import { ConversationChain } from 'langchain/chains';
import { Document } from '@langchain/core/documents';
import { uniqBy, sortBy } from 'es-toolkit';
export class SemanticMemoryManager {
private vectorStore: MemoryVectorStore;
private memory: VectorStoreRetrieverMemory;
private chain: ConversationChain;
private embeddings: GoogleGenerativeAIEmbeddings;
constructor() {
this.embeddings = new GoogleGenerativeAIEmbeddings({
modelName: 'embedding-001',
});
this.vectorStore = new MemoryVectorStore(this.embeddings);
this.memory = new VectorStoreRetrieverMemory({
vectorStoreRetriever: this.vectorStore.asRetriever(5),
memoryKey: 'history',
inputKey: 'input',
outputKey: 'response',
returnDocs: true,
});
const model = new ChatGoogleGenerativeAI({
modelName: 'gemini-2.5-pro',
temperature: 0.7,
});
this.chain = new ConversationChain({
llm: model,
memory: this.memory,
});
}
async addMemory(content: string, metadata: Record<string, any> = {}) {
const doc = new Document({
pageContent: content,
metadata: {
...metadata,
timestamp: Date.now(),
},
});
await this.vectorStore.addDocuments([doc]);
}
async searchMemories(query: string, k: number = 5) {
const results = await this.vectorStore.similaritySearchWithScore(query, k);
// 관련성과 최신성으로 정렬
const sorted = sortBy(results, [
(r) => -r[1], // 점수 (내림차순)
(r) => -r[0].metadata.timestamp, // 타임스탬프 (내림차순)
]);
return sorted.map(([doc, score]) => ({
content: doc.pageContent,
metadata: doc.metadata,
relevanceScore: score,
}));
}
async processWithSemanticMemory(message: string) {
// 관련 과거 상호작용 검색
const relevantMemories = await this.searchMemories(message, 3);
// 관련 컨텍스트를 대화에 추가
const contextualMessage = relevantMemories.length > 0
? `관련 컨텍스트: ${relevantMemories.map(m => m.content).join('\n')}\n\n사용자: ${message}`
: message;
const result = await this.chain.call({ input: contextualMessage });
// 상호작용 저장
await this.addMemory(
`사용자: ${message}\n어시스턴트: ${result.response}`,
{ type: 'conversation' }
);
return {
response: result.response,
relevantMemories: relevantMemories.slice(0, 2),
totalMemories: await this.vectorStore.memoryVectors.length,
};
}
}
벡터 임베딩을 사용하여 최신성보다는 의미에 기반한 관련 과거 상호작용을 찾는 의미적 메모리 시스템을 생성합니다.
3. 서버리스를 위한 Redis를 사용한 지속적 메모리
// lib/memory/redis-memory.ts
import { Redis } from '@upstash/redis';
import { BufferMemory, ChatMessageHistory } from 'langchain/memory';
import { AIMessage, HumanMessage, BaseMessage } from '@langchain/core/messages';
import { ChatGoogleGenerativeAI } from '@langchain/google-genai';
import { ConversationChain } from 'langchain/chains';
import { memoize, chunk } from 'es-toolkit';
const redis = new Redis({
url: process.env.UPSTASH_REDIS_URL!,
token: process.env.UPSTASH_REDIS_TOKEN!,
});
export class RedisMemoryManager {
private sessionTTL = 3600; // 1시간
private maxMessagesPerSession = 100;
// 성능을 위한 세션 로딩 메모이즈
private loadSession = memoize(
async (sessionId: string) => {
const data = await redis.get(`session:${sessionId}`);
return data as any || { messages: [], metadata: {} };
},
{
resolver: (sessionId) => sessionId,
ttl: 5000, // 5초 동안 캐시
}
);
async getMemory(sessionId: string): Promise<BufferMemory> {
const session = await this.loadSession(sessionId);
const messages = session.messages.map((msg: any) =>
msg.type === 'human'
? new HumanMessage(msg.content)
: new AIMessage(msg.content)
);
const chatHistory = new ChatMessageHistory(messages);
return new BufferMemory({
chatHistory,
returnMessages: true,
memoryKey: 'history',
});
}
async saveMemory(sessionId: string, messages: BaseMessage[]) {
// 서버리스 제약을 위해 최근 메시지만 유지
const recentMessages = messages.slice(-this.maxMessagesPerSession);
const serialized = recentMessages.map(msg => ({
type: msg._getType(),
content: msg.content,
}));
const session = {
messages: serialized,
metadata: {
lastAccess: Date.now(),
messageCount: serialized.length,
},
};
await redis.setex(
`session:${sessionId}`,
this.sessionTTL,
JSON.stringify(session)
);
// 정리를 위한 세션 인덱스 업데이트
await redis.zadd('sessions:active', {
score: Date.now(),
member: sessionId,
});
}
async processWithPersistence(
sessionId: string,
message: string
) {
const model = new ChatGoogleGenerativeAI({
modelName: 'gemini-2.5-flash',
temperature: 0.7,
});
const memory = await this.getMemory(sessionId);
const chain = new ConversationChain({
llm: model,
memory,
});
const result = await chain.call({ input: message });
// 업데이트된 메모리 저장
await this.saveMemory(
sessionId,
chain.memory.chatHistory.messages
);
// 세션 통계 가져오기
const stats = await redis.get(`session:${sessionId}:stats`) as any || {};
stats.messageCount = (stats.messageCount || 0) + 1;
stats.lastActive = Date.now();
await redis.setex(
`session:${sessionId}:stats`,
this.sessionTTL,
JSON.stringify(stats)
);
return {
response: result.response,
sessionId,
stats,
};
}
async cleanupOldSessions() {
// 24시간보다 오래된 세션 제거
const cutoff = Date.now() - 24 * 60 * 60 * 1000;
const oldSessions = await redis.zrangebyscore(
'sessions:active',
0,
cutoff
);
// 효율성을 위해 청크로 일괄 삭제
const sessionChunks = chunk(oldSessions, 10);
for (const batch of sessionChunks) {
await Promise.all(
batch.map(sessionId =>
redis.del(`session:${sessionId}`)
)
);
}
await redis.zremrangebyscore('sessions:active', 0, cutoff);
return oldSessions.length;
}
}
자동 정리 및 세션 관리를 통해 Vercel의 서버리스 환경에 최적화된 Redis 기반 지속적 메모리를 구현합니다.
4. 프로필 저장소를 가진 계층적 메모리
// lib/memory/hierarchical-memory.ts
import { StateGraph, Annotation, MemorySaver } from '@langchain/langgraph';
import { ChatGoogleGenerativeAI } from '@langchain/google-genai';
import { Redis } from '@upstash/redis';
import { pick, omit, merge } from 'es-toolkit';
interface UserProfile {
preferences: Record<string, any>;
facts: string[];
interests: string[];
lastUpdated: number;
}
interface SessionMemory {
messages: any[];
summary: string;
topics: string[];
}
interface WorkingMemory {
currentTopic: string;
context: string[];
activeGoal: string;
}
const HierarchicalStateAnnotation = Annotation.Root({
// 작업 메모리 - 즉시 컨텍스트
working: Annotation<WorkingMemory>({
reducer: (x, y) => ({ ...x, ...y }),
default: () => ({
currentTopic: '',
context: [],
activeGoal: '',
}),
}),
// 세션 메모리 - 현재 대화
session: Annotation<SessionMemory>({
reducer: (x, y) => ({ ...x, ...y }),
default: () => ({
messages: [],
summary: '',
topics: [],
}),
}),
// 장기 메모리 - 사용자 프로필
profile: Annotation<UserProfile>({
reducer: (x, y) => merge(x, y),
default: () => ({
preferences: {},
facts: [],
interests: [],
lastUpdated: Date.now(),
}),
}),
});
export class HierarchicalMemorySystem {
private redis: Redis;
private graph: ReturnType<typeof StateGraph.compile>;
private model: ChatGoogleGenerativeAI;
constructor() {
this.redis = new Redis({
url: process.env.UPSTASH_REDIS_URL!,
token: process.env.UPSTASH_REDIS_TOKEN!,
});
this.model = new ChatGoogleGenerativeAI({
modelName: 'gemini-2.5-pro',
temperature: 0.7,
});
this.graph = this.buildMemoryGraph();
}
private buildMemoryGraph() {
const workflow = new StateGraph(HierarchicalStateAnnotation)
.addNode('processInput', async (state) => {
// 입력에서 주제와 의도 추출
const analysis = await this.model.invoke([
new SystemMessage('주요 주제와 사용자 의도를 JSON으로 추출'),
new HumanMessage(state.working.context[state.working.context.length - 1]),
]);
try {
const { topic, intent } = JSON.parse(analysis.content as string);
return {
working: {
currentTopic: topic,
activeGoal: intent,
},
};
} catch {
return {};
}
})
.addNode('retrieveRelevantMemory', async (state) => {
// 관련 장기 메모리 가져오기
const profileKey = `profile:${state.working.currentTopic}`;
const relevantProfile = await this.redis.get(profileKey) || {};
// 관련 세션 메모리 가져오기
const sessionKey = `session:recent:${state.working.currentTopic}`;
const relevantSessions = await this.redis.get(sessionKey) || [];
return {
working: {
context: [
...state.working.context,
`프로필 컨텍스트: ${JSON.stringify(relevantProfile)}`,
`이전 논의: ${JSON.stringify(relevantSessions)}`,
],
},
};
})
.addNode('generateResponse', async (state) => {
// 전체 컨텍스트로 응답 생성
const contextMessage = [
new SystemMessage(`사용자 프로필: ${JSON.stringify(state.profile)}`),
new SystemMessage(`현재 주제: ${state.working.currentTopic}`),
new SystemMessage(`세션 컨텍스트: ${state.session.summary}`),
...state.working.context.map(c => new SystemMessage(c)),
];
const response = await this.model.invoke(contextMessage);
return {
session: {
messages: [...state.session.messages, response.content],
},
};
})
.addNode('updateMemoryTiers', async (state) => {
// 중요한 정보를 계층 상위로 승격
const importantFacts = await this.extractImportantFacts(
state.session.messages
);
if (importantFacts.length > 0) {
return {
profile: {
facts: [...state.profile.facts, ...importantFacts],
lastUpdated: Date.now(),
},
};
}
return {};
})
.addNode('persistMemory', async (state) => {
// 다른 TTL로 Redis에 저장
// 작업 메모리 - 5분
await this.redis.setex(
`working:${state.working.currentTopic}`,
300,
JSON.stringify(state.working)
);
// 세션 메모리 - 1시간
await this.redis.setex(
`session:${Date.now()}`,
3600,
JSON.stringify(state.session)
);
// 프로필 - 영구 (또는 매우 긴 TTL)
await this.redis.set(
`profile:user`,
JSON.stringify(state.profile)
);
return {};
});
workflow.addEdge('__start__', 'processInput');
workflow.addEdge('processInput', 'retrieveRelevantMemory');
workflow.addEdge('retrieveRelevantMemory', 'generateResponse');
workflow.addEdge('generateResponse', 'updateMemoryTiers');
workflow.addEdge('updateMemoryTiers', 'persistMemory');
workflow.addEdge('persistMemory', '__end__');
const checkpointer = new MemorySaver();
return workflow.compile({ checkpointer });
}
private async extractImportantFacts(messages: string[]): Promise<string[]> {
if (messages.length === 0) return [];
const extraction = await this.model.invoke([
new SystemMessage('사용자에 대한 중요한 사실을 JSON 배열로 추출'),
...messages.map(m => new HumanMessage(m)),
]);
try {
return JSON.parse(extraction.content as string);
} catch {
return [];
}
}
async process(userId: string, message: string) {
// 기존 프로필 로드
const profile = await this.redis.get(`profile:${userId}`) as UserProfile || {
preferences: {},
facts: [],
interests: [],
lastUpdated: Date.now(),
};
const result = await this.graph.invoke(
{
working: {
context: [message],
},
profile,
},
{
configurable: { thread_id: userId },
}
);
return {
response: result.session.messages[result.session.messages.length - 1],
memoryStats: {
workingMemorySize: result.working.context.length,
sessionMessages: result.session.messages.length,
profileFacts: result.profile.facts.length,
},
};
}
}
즉시 컨텍스트를 위한 작업 메모리, 대화를 위한 세션 메모리, 장기 프로필 저장을 위한 3계층 메모리 계층을 구현합니다.
5. 메모리 상태를 가진 프론트엔드 통합
// components/MemoryChat.tsx
'use client';
import { useState, useEffect } from 'react';
import { useMutation } from '@tanstack/react-query';
import { groupBy, debounce } from 'es-toolkit';
interface MemoryStats {
workingMemory: number;
sessionMessages: number;
profileFacts: number;
relevantMemories?: Array<{
content: string;
relevanceScore: number;
}>;
}
export default function MemoryChat() {
const [message, setMessage] = useState('');
const [sessionId] = useState(() =>
`session-${Date.now()}-${Math.random()}`
);
const [memoryStats, setMemoryStats] = useState<MemoryStats>({
workingMemory: 0,
sessionMessages: 0,
profileFacts: 0,
});
const [messages, setMessages] = useState<Array<{
role: 'user' | 'assistant';
content: string;
timestamp: number;
}>>([]);
const sendMessage = useMutation({
mutationFn: async (text: string) => {
const response = await fetch('/api/memory-chat', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
message: text,
sessionId
}),
});
return response.json();
},
onSuccess: (data) => {
setMessages(prev => [
...prev,
{
role: 'user',
content: message,
timestamp: Date.now()
},
{
role: 'assistant',
content: data.response,
timestamp: Date.now()
},
]);
setMemoryStats(data.memoryStats || memoryStats);
setMessage('');
},
});
// 주기적으로 세션 자동 저장
useEffect(() => {
const saveSession = debounce(async () => {
await fetch('/api/memory-chat/save', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ sessionId, messages }),
});
}, 10000);
if (messages.length > 0) {
saveSession();
}
}, [messages, sessionId]);
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
if (message.trim()) {
sendMessage.mutate(message);
}
};
// 시간대별 메시지 그룹화
const messageGroups = groupBy(messages, (msg) => {
const date = new Date(msg.timestamp);
return date.toLocaleDateString();
});
return (
<div className="flex flex-col h-screen max-h-[800px] bg-base-100">
{/* 메모리 상태 바 */}
<div className="navbar bg-base-200 px-4">
<div className="flex-1">
<h2 className="text-xl font-bold">AI 메모리 채팅</h2>
</div>
<div className="flex-none">
<div className="stats stats-horizontal shadow">
<div className="stat place-items-center py-2 px-4">
<div className="stat-title text-xs">작업</div>
<div className="stat-value text-lg">
{memoryStats.workingMemory}
</div>
</div>
<div className="stat place-items-center py-2 px-4">
<div className="stat-title text-xs">세션</div>
<div className="stat-value text-lg">
{memoryStats.sessionMessages}
</div>
</div>
<div className="stat place-items-center py-2 px-4">
<div className="stat-title text-xs">프로필</div>
<div className="stat-value text-lg">
{memoryStats.profileFacts}
</div>
</div>
</div>
</div>
</div>
{/* 메시지 영역 */}
<div className="flex-1 overflow-y-auto p-4 space-y-4">
{Object.entries(messageGroups).map(([date, msgs]) => (
<div key={date}>
<div className="divider text-sm">{date}</div>
{msgs.map((msg, idx) => (
<div
key={idx}
className={`chat ${
msg.role === 'user' ? 'chat-end' : 'chat-start'
}`}
>
<div className="chat-header">
{msg.role === 'user' ? '사용자' : 'AI'}
</div>
<div
className={`chat-bubble ${
msg.role === 'user'
? 'chat-bubble-primary'
: 'chat-bubble-secondary'
}`}
>
{msg.content}
</div>
</div>
))}
</div>
))}
{/* 사용 가능한 경우 관련 메모리 표시 */}
{memoryStats.relevantMemories &&
memoryStats.relevantMemories.length > 0 && (
<div className="alert alert-info">
<div>
<h4 className="font-bold">관련 메모리:</h4>
{memoryStats.relevantMemories.map((mem, idx) => (
<div key={idx} className="text-sm mt-1">
• {mem.content} (관련성: {mem.relevanceScore.toFixed(2)})
</div>
))}
</div>
</div>
)}
{sendMessage.isPending && (
<div className="flex justify-start">
<div className="chat-bubble">
<span className="loading loading-dots loading-sm"></span>
</div>
</div>
)}
</div>
{/* 입력 영역 */}
<form onSubmit={handleSubmit} className="p-4 bg-base-200">
<div className="join w-full">
<input
type="text"
value={message}
onChange={(e) => setMessage(e.target.value)}
placeholder="메시지를 입력하세요..."
className="input input-bordered join-item flex-1"
disabled={sendMessage.isPending}
/>
<button
type="submit"
className="btn btn-primary join-item"
disabled={sendMessage.isPending || !message.trim()}
>
전송
</button>
</div>
</form>
</div>
);
}
작업 메모리, 세션 메시지, 프로필 사실을 실시간으로 보여주는 메모리 통계를 시각화하는 React 컴포넌트입니다.
결론
메모리 관리는 상태가 없는 AI 상호작용을 시간이 지나면서 관계를 구축하는 지능적이고 컨텍스트 인식 대화로 변환합니다. 이 가이드는 간단한 버퍼 메모리부터 Vercel의 서버리스 제약에 최적화된 정교한 계층적 시스템까지의 진행을 보여주었습니다. 핵심 패턴은 복잡한 메모리 플로우를 위한 LangGraph의 StateGraph 사용, 서버리스 지속성을 위한 Redis 구현, 의미적 검색을 위한 벡터 저장소 활용, 인간 인지 시스템을 반영하는 멀티 티어 아키텍처 구축을 포함합니다. 작업, 에피소드, 의미적, 절차적 메모리의 조합은 에이전트가 컨텍스트를 유지하고, 상호작용에서 학습하며, 서버리스 실행 제한 내에서 효율적으로 작동하면서 점점 개인화된 지원을 제공할 수 있게 합니다.