6b017b0d31
- Datacollect Bridge 로컬/NAS 타깃 토글(Settings 패널) + NAS URL/x-bridge-token. 기본 local = 현행 동작 유지. (백엔드 NAS 분리 준비) - /research(NotebookLM) 제거 — 로컬 Datacollect 앱 전용으로 분리. - 에러로그 오염 차단: STT/스택트레이스/에러덤프를 장기기억 채굴 제외 + 자동 추출 항목 14일 TTL(참조 시 슬라이딩 연장). 기존·수동 항목 무영향. - 컨텍스트 [주제] 태깅 + 교차오염 방지 경계 지침. - "확인 불가" 사실 날조 금지 규칙(R7과 구분). - /meet STT 오타 보정: 철자 정규화 허용하되 사실 날조는 차단. 타입체크 + 407 테스트 통과. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
121 lines
4.2 KiB
TypeScript
121 lines
4.2 KiB
TypeScript
/**
|
||
* ============================================================
|
||
* 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;
|
||
}
|
||
}
|