Current similarity calculation uses simple embedding cosine similarity or word overlap, failing to distinguish semantic context when the same keywords appear in different domains.
Failure Example: CREATE_010
Memory A (Company Growth):
회사 성장 전략 회의:매출 증대 방안 논의.
목표:전년 대비 30% 성장
Memory B (Employee Growth):
직원 성장 프로그램:직무 교육,멘토링,리더십 과정.
대상:전 직원. 신청 마감:2월 10일
Current Problem:
Keyword "성장" (growth) appears in both
Simple embedding similarity may be HIGH (0.65+)
But contexts are completely different:
A: Business/Revenue domain
B: HR/Training domain
Expected: CREATE new memory (different context)
Risk: System may incorrectly mark as RELATED and create unwanted link
Root Cause
// Current approach (oversimplified)constcontent_similarity = embedding_cosine(A,B);// → 0.70// Problem: Single number cannot capture:// - Domain difference (business vs HR)// - Intent difference (strategy meeting vs program announcement)// - Entity type difference (company vs employees)
Design Goals
Primary Goals
Context Awareness: Distinguish same keywords in different semantic contexts
Nuance Detection: Capture subtle differences (update vs new initiative, cause vs effect, etc.)
Explainability: Provide reasoning for similarity judgments
Production Viable: Maintain acceptable latency and cost
Non-Goals
Perfect Accuracy: Not aiming for 100% (human-level judgment varies)
Real-time Learning: Not implementing online learning from feedback (v1)
Multi-hop Reasoning: Not inferring implicit relationships beyond direct comparison
Approach: Semantic Decomposition
Core Idea
Instead of comparing raw content embeddings, decompose each memory into structured semantic components and compare at multiple levels.
interface SemanticDecomposition {// Core semantic elements
core:{
subject:string;// Main entity/topic
action:string;// What's happening
objects:string[];// What's being acted upon};// Contextual metadata
context:{
domain:DomainType;// Business area
intent:IntentType;// Purpose of communication
temporalContext:string;// Time reference (Q1, 2025, etc.)spatialContext?: string;// Location if relevant};// Extracted entities
entities:{
people:string[];
organizations:string[];
projects:string[];
concepts:string[];// Abstract concepts};// Relationship indicators
relationships:{
isUpdate:boolean;// Updates existing info?
references:string[];// Mentions other topicscausality?:{// Cause-effect relationshipcause?: string;effect?: string;};};}enum DomainType {BUSINESS_STRATEGY = 'business_strategy',FINANCE = 'finance',HR = 'hr',MARKETING = 'marketing',ENGINEERING = 'engineering',OPERATIONS = 'operations',LEGAL = 'legal',GENERAL = 'general',}enum IntentType {INFORM = 'inform',// Sharing informationREQUEST = 'request',// Asking for somethingDECISION = 'decision',// Announcing decisionDISCUSSION = 'discussion',// Ongoing conversationREPORT = 'report',// Status updateANNOUNCEMENT = 'announcement',// Broadcast}
Example Decomposition
Memory A (Company Growth):
{"core":{"subject":"회사 성장 전략","action":"회의 및 논의","objects":["매출 증대 방안"]},"context":{"domain":"business_strategy","intent":"discussion","temporalContext":"2025"},"entities":{"people":["ceo@company.com","executives@company.com"],"organizations":["회사"],"projects":[],"concepts":["성장","매출","전략"]},"relationships":{"isUpdate":false,"references":[],"causality":null}}
Memory B (Employee Growth):
{"core":{"subject":"직원 성장 프로그램","action":"런칭 및 모집","objects":["직무 교육","멘토링","리더십 과정"]},"context":{"domain":"hr","intent":"announcement","temporalContext":"신청 마감: 2월 10일"},"entities":{"people":["hr@company.com","all@company.com"],"organizations":["HR"],"projects":["직원 성장 프로그램"],"concepts":["성장","교육","멘토링"]},"relationships":{"isUpdate":false,"references":[],"causality":null}}
Multi-Level Similarity Calculation
Level 1: Domain Match
functioncalculateDomainSimilarity(domainA: DomainType,domainB: DomainType
): number {// Exact matchif(domainA === domainB)return1.0;// Related domains (configurable)constDOMAIN_RELATIONS: Record<string,Record<string,number>> = {'business_strategy':{'finance':0.7,'marketing':0.6},'finance':{'business_strategy':0.7,'operations':0.5},'hr':{'operations':0.4},// etc.};returnDOMAIN_RELATIONS[domainA]?.[domainB] || 0.0;}
Example:
business_strategy vs hr: 0.0 (unrelated)
business_strategy vs finance: 0.7 (related)
Level 2: Core Subject Similarity
functioncalculateCoreSimilarity(coreA: SemanticDecomposition['core'],coreB: SemanticDecomposition['core']): number {// Use embedding similarity on structured componentsconstsubjectSim = embeddingSimilarity(coreA.subject,coreB.subject);constactionSim = embeddingSimilarity(coreA.action,coreB.action);constobjectsSim = jaccardSimilarity(coreA.objects,coreB.objects);// Weighted combinationreturnsubjectSim * 0.5 + actionSim * 0.25 + objectsSim * 0.25;}
{"overall_score":0.28,"category":"UNRELATED","breakdown":{"domain_match":0.0,"core_similarity":0.39,"entity_overlap":0.05,"context_similarity":0.35,"raw_embedding":0.68},"same_context":false,"context_distance":0.76,"reasoning":"두 메모리는 '성장'이라는 키워드를 공유하지만 완전히 다른 맥락입니다. Memory A는 business_strategy 도메인의 회사 매출 성장에 관한 것이고, Memory B는 hr 도메인의 직원 교육 프로그램에 관한 것입니다. 도메인이 다르고 핵심 주제가 다르므로 별개의 메모리로 취급해야 합니다."}
Key Insight: Raw embedding was 0.68 (RELATED), but contextual analysis correctly identified as UNRELATED (0.28).
LLM-Based Semantic Decomposition
Extraction Prompt
constDECOMPOSITION_PROMPT = `
당신은 이메일 내용을 분석하여 구조화된 semantic 정보를 추출하는 전문가입니다.
다음 메모리 내용을 분석하여 JSON 형식으로 분해해주세요:
메모리 내용:
"""
{content}
"""
출력 형식:
{
"core": {
"subject": "핵심 주제 (예: Q1 마케팅 캠페인)",
"action": "진행되는 행동 (예: 예산 승인, 회의 개최)",
"objects": ["행동의 대상들"]
},
"context": {
"domain": "business_strategy|finance|hr|marketing|engineering|operations|legal|general 중 하나",
"intent": "inform|request|decision|discussion|report|announcement 중 하나",
"temporalContext": "시간 관련 언급 (예: Q1, 2025년, 2월 10일 마감)"
},
"entities": {
"people": ["이메일 주소들"],
"organizations": ["조직명, 팀명"],
"projects": ["프로젝트명"],
"concepts": ["핵심 개념 키워드들"]
},
"relationships": {
"isUpdate": true/false,
"references": ["언급된 다른 주제들"],
"causality": {
"cause": "원인",
"effect": "결과"
}
}
}
분석 시 주의사항:
1. domain을 신중하게 선택하세요 (같은 키워드도 다른 domain일 수 있음)
2. subject는 구체적으로 명시하세요
3. concepts는 핵심 키워드만 포함하세요 (너무 많지 않게)
JSON만 출력하세요:
`;asyncfunctionextractSemanticDecomposition(content: string,llmClient: LLMClient
):Promise<SemanticDecomposition>{
const prompt = DECOMPOSITION_PROMPT.replace('{content}', content);
const response = await llmClient.generate({prompt,model: 'gpt-4o-mini', // Fast, cheap model
temperature: 0.0, // Deterministic
response_format: {type
Alternative: Direct LLM Judgment
For cases where latency is acceptable, use LLM to directly judge context similarity with few-shot examples.
Contrastive Few-Shot Prompt
interface ContrastiveExample {
memoryA:string;
memoryB:string;
judgment:'SAME_CONTEXT' | 'RELATED_CONTEXT' | 'DIFFERENT_CONTEXT';
reasoning:string;}constFEW_SHOT_EXAMPLES: ContrastiveExample[] = [{memoryA:"Q1 마케팅 캠페인 예산 5000만원 승인",memoryB:"Q1 마케팅 캠페인 예산 6000만원으로 증액",judgment:'SAME_CONTEXT',reasoning:"같은 Q1 마케팅 캠페인의 예산 정보. 금액만 변경됨.",},{memoryA:"Q1 OKR: 사용자 증가 20% 목표",memoryB:"Q2 OKR: 사용자 증가 15% 목표 설정",judgment:'RELATED_CONTEXT',reasoning:"다른 분기의 OKR이지만 연속성 있는 관련 정보",},{memoryA:"회사 성장 전략 회의: 매출 증대 방안",memoryB:"직원 성장 프로그램: 교육 및 멘토링",judgment:'DIFFERENT_CONTEXT',reasoning:"'성장' 키워드는 같지만 완전히 다른 맥락. 회사 매출 vs 직원 개발",},// More examples...];constJUDGMENT_PROMPT = `
당신은 두 메모리가 같은 맥락(context)인지 판단하는 전문가입니다.
예시들:
${FEW_SHOT_EXAMPLES.map(ex=>`
Memory A: "${ex.memoryA}"
Memory B: "${ex.memoryB}"
판단: ${ex.judgment}
이유: ${ex.reasoning}
`).join('\n')}
이제 다음을 판단해주세요:
Memory A: "${memoryA}"
Memory B: "${memoryB}"
JSON 형식으로 답변:
{
"judgment": "SAME_CONTEXT|RELATED_CONTEXT|DIFFERENT_CONTEXT",
"confidence": 0.0~1.0,
"reasoning": "판단 근거 (한국어)",
"key_factors": ["판단에 중요한 요소들"]
}
`;asyncfunctionjudgeContextSimilarity(memoryA: string,memoryB: string,llmClient: LLMClient
):Promise<ContextJudgment>{
const response = await llmClient.generate({prompt: JUDGMENT_PROMPT.replace('{memoryA}', memoryA).replace('{memoryB}
Hybrid Approach (Recommended)
Combine both approaches for optimal cost/latency/accuracy:
asyncfunctioncalculateSimilarity(memoryA: MemoryNode,memoryB: MemoryNode
):Promise<ContextualSimilarityResult>{// Step 1: Fast embedding similarity
const rawSim = cosineSimilarity(memoryA.embedding, memoryB.embedding);
// Step 2: Clear cases - skip LLM
if (rawSim >= 0.98) {// Almost identical - SKIP LLMreturn{overall_score:rawSim,category:'DUPLICATE',...};
}
if (rawSim < 0.30) {// Clearly unrelated - SKIP LLMreturn{overall_score:rawSim,category:'UNRELATED',...};
}
// Step 3: Boundary cases (0.30-0.98) - use contextual analysis
// Option A: Semantic decomposition + multi-level calc (faster, cheaper)
const decompositionA = await extractSemanticDecomposition(memoryA.content, llm);
const decompositionB = await extractSemanticDecomposition(memoryB.content, llm);
const contextualSim = calculateContextualSimilarity(
decompositionA, decompositionB, memoryA.embedding, memoryB.embedding
);
// Option B: Direct LLM judgment (slower, more accurate for ambiguous cases)
// Use for very ambiguous cases (rawSim 0.45-0.85)
if (rawSim > 0.45 && rawSim < 0.85) {
Performance Optimization
Caching Strategy
interface DecompositionCache {
memoryId:string;
decomposition:SemanticDecomposition;
cachedAt:Date;
ttl:number;// Time to live (seconds)}class SemanticCache {privatecache: Map<string, DecompositionCache> = new Map();
async getOrCompute(
memoryId: string,
content: string,
llm: LLMClient
): Promise<SemanticDecomposition>{
const cached = this.cache.get(memoryId);
if (cached && !this.isExpired(cached)) {
return cached.decomposition;
}
// Compute and cache
const decomposition = await extractSemanticDecomposition(content, llm);
this.cache.set(memoryId, {memoryId,decomposition,cachedAt