/** * ============================================================ * Memory Extractor (기억 추출기) * * 대화 종료 시 히스토리를 분석하여 각 메모리 레이어에 * 저장할 정보를 자동으로 추출합니다. * LLM 호출 없이 패턴 매칭 기반으로 동작합니다. * ============================================================ */ import { LongTermMemory } from './LongTermMemory'; import { ProjectMemory } from './ProjectMemory'; import { EpisodicMemory } from './EpisodicMemory'; interface ExtractionResult { longTermCandidates: number; episodeCreated: boolean; projectUpdated: boolean; } export class MemoryExtractor { /** * 세션 종료 시 모든 메모리 레이어에 대해 추출을 수행합니다. */ public extractFromSession( sessionId: string, messages: Array<{ role: string; content: string; timestamp?: number }>, longTermMemory: LongTermMemory, episodicMemory: EpisodicMemory, projectMemory: ProjectMemory | null, projectContext?: string ): ExtractionResult { const result: ExtractionResult = { longTermCandidates: 0, episodeCreated: false, projectUpdated: false }; // 1. Long-Term Memory 추출 // 자동 추출 항목엔 TTL(14일)을 부여 — 참조될 때마다 슬라이딩 연장되므로 실제로 // 쓰이는 지식은 살아남고, 한 번 들어온 일회성·잡음 내용은 14일 뒤 자연 소멸한다. // (에러 로그/실패 데이터는 extractCandidates 단계에서 이미 걸러짐.) const candidates = LongTermMemory.extractCandidates(messages); const expiresAt = Date.now() + LongTermMemory.AUTO_EXTRACT_TTL_MS; for (const candidate of candidates) { longTermMemory.addEntry( candidate.category, candidate.content, `session:${sessionId}`, 0.7, // 자동 추출이므로 기본 신뢰도 0.7 { expiresAt }, ); } result.longTermCandidates = candidates.length; // 2. Episodic Memory 생성 const episode = episodicMemory.createEpisode( sessionId, messages, projectContext ); result.episodeCreated = !!episode; // 3. Project Memory 업데이트 (프로젝트 관련 대화인 경우) if (projectMemory && projectContext) { const updated = this.extractProjectInfo(messages, projectMemory); result.projectUpdated = updated; } return result; } /** * 대화에서 프로젝트 관련 정보를 추출하여 Project Memory에 저장합니다. */ private extractProjectInfo( messages: Array<{ role: string; content: string }>, projectMemory: ProjectMemory ): boolean { let updated = false; const allText = messages.map((m) => m.content).join('\n'); // Tech stack 추출 const techPatterns = [ /(?:사용|using|사용하는|tech\s*stack|기술\s*스택)[\s::]*([^\n]+)/gi ]; for (const pattern of techPatterns) { let match; while ((match = pattern.exec(allText)) !== null) { const techs = match[1] .split(/[,,\s]+/) .filter((t) => t.length >= 2 && t.length <= 20); for (const tech of techs) { projectMemory.addTechStack(tech.trim()); updated = true; } } } // Bug report 추출 const bugPatterns = [ /(?:버그|bug|오류|error|이슈|issue)[\s::]+(.{10,200})/gi ]; for (const pattern of bugPatterns) { let match; while ((match = pattern.exec(allText)) !== null) { // 간단한 버그만 자동 기록 (상세 분석은 사용자 확인 필요) // 여기서는 패턴만 감지하고, 실제 기록은 사용자 확인 후 updated = true; } } if (updated) { projectMemory.save(); } return updated; } }