From 8d6e510b00b03511ae14c29cece4fc1f10c8e6ae Mon Sep 17 00:00:00 2001 From: g1nation Date: Tue, 5 May 2026 10:50:20 +0900 Subject: [PATCH] perf(scoring): optimized retrieval engine with caching and IDF smoothing v2.69.0 --- package-lock.json | 4 ++-- package.json | 2 +- src/retrieval/scoring.ts | 44 +++++++++++++++++++++++++++++++++++++--- 3 files changed, 44 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2f59f52..0198776 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "g1nation", - "version": "2.68.0", + "version": "2.69.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "g1nation", - "version": "2.68.0", + "version": "2.69.0", "license": "MIT", "dependencies": { "marked": "^18.0.2" diff --git a/package.json b/package.json index 3fb52f3..08ae2d4 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "astra", "displayName": "Astra", "description": "The personal intelligence layer for Antigravity and VS Code. A private cognitive partner for deep project context, memory, and proactive strategic decision-making.", - "version": "2.68.0", + "version": "2.69.0", "publisher": "g1nation", "license": "MIT", "icon": "assets/icon.png", diff --git a/src/retrieval/scoring.ts b/src/retrieval/scoring.ts index 0b3b6f0..bb8f7c4 100644 --- a/src/retrieval/scoring.ts +++ b/src/retrieval/scoring.ts @@ -28,16 +28,43 @@ const STOP_WORDS_KO = new Set([ '대한', '대해', '통해', '따라', '위해', '대로', '만큼' ]); +// ─── Internal Cache for Tokenization ─── +const TOKEN_CACHE = new Map(); +const MAX_CACHE_SIZE = 1000; + /** - * 한국어/영어 혼합 텍스트를 토큰으로 분리합니다. + * 한국어/영어 혼합 텍스트를 정규화하고 토큰으로 분리합니다. + * (Performance Optimization: 내부 캐시 적용) */ export function tokenize(text: string): string[] { - return text + if (!text) return []; + + // 캐시 확인 + if (TOKEN_CACHE.has(text)) { + return TOKEN_CACHE.get(text)!; + } + + // 1. Pre-normalization: 특수문자 정제 및 표준화 + const normalized = text .toLowerCase() + .replace(/[\u200B-\u200D\uFEFF]/g, '') // Zero-width spaces 제거 + .replace(/[^\w\s가-힣_.-]/g, ' ') // 허용된 문자 외에는 공백 처리 + .trim(); + + // 2. Tokenization: 정제된 텍스트 분리 + const tokens = normalized .split(/[^a-z0-9가-힣_.-]+/g) .map((t) => t.trim()) .filter((t) => t.length >= 2) .filter((t) => !STOP_WORDS_EN.has(t) && !STOP_WORDS_KO.has(t)); + + // 캐시 저장 (메모리 관리: 임계값 초과 시 비우기) + if (TOKEN_CACHE.size >= MAX_CACHE_SIZE) { + TOKEN_CACHE.clear(); + } + TOKEN_CACHE.set(text, tokens); + + return tokens; } /** @@ -90,13 +117,24 @@ function termFrequency(term: string, documentTokens: string[]): number { /** * IDF (Inverse Document Frequency): 전체 문서 대비 희소도 + * (Stability Enhancement: Smoothing 적용 및 최소 문서 수 대응) */ function inverseDocumentFrequency( term: string, allDocumentTokenSets: Array> ): number { + const N = allDocumentTokenSets.length; + if (N === 0) return 1.0; + const containing = allDocumentTokenSets.filter((doc) => doc.has(term)).length; - return Math.log((allDocumentTokenSets.length + 1) / (containing + 1)) + 1; + + // N이 매우 작을 때(예: 5개 이하) 스코어 편향 방지를 위한 최소 분모 보정 + const smoothN = N < 5 ? N + 5 : N; + const smoothContaining = containing; + + // Standard Smooth IDF: log((N+1) / (containing+1)) + 1 + // containing이 0일 경우에도 안전하게 동작하도록 설계 + return Math.log((smoothN + 1) / (smoothContaining + 1)) + 1; } export interface ScoredDocument {