feat: migrate embeddings to Candle WASM + remove semantic type inference
Major architectural changes: 1. EMBEDDINGS ENGINE (ONNX → Candle WASM): - Replace ONNX Runtime with Rust Candle compiled to WASM - Embedded model in WASM binary (no external downloads) - Quantized Q8 precision with <50MB memory footprint - Zero-download, offline-first operation - Same embedding quality (all-MiniLM-L6-v2) 2. REMOVE SEMANTIC TYPE INFERENCE: - Delete embeddedKeywordEmbeddings.ts (14MB of pre-computed embeddings) - Remove typeAwareQueryPlanner.ts and semanticTypeInference.ts - Remove VerbExactMatchSignal (uses keyword embeddings) - Update SmartRelationshipExtractor to 3 signals (55%/30%/15% weights) API CHANGES (requires v7.0.0): - Removed: inferTypes(), inferNouns(), inferVerbs(), inferIntent() - Removed: getSemanticTypeInference(), SemanticTypeInference class - Removed: TypeInference, SemanticTypeInferenceOptions types Users can still use natural language queries in find() - they just need to specify type explicitly for type-optimized searches. PACKAGE SIZE IMPACT: - Compressed: 90.1 MB → 86.2 MB (-4.3%) - Uncompressed: 114.4 MB → 100.3 MB (-12%) - ~448K lines of code removed 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
81cd16e41b
commit
da7d2ed29d
60 changed files with 3887 additions and 448557 deletions
|
|
@ -10,10 +10,9 @@
|
|||
* - Comprehensive relationship intelligence built-in
|
||||
*
|
||||
* Ensemble Architecture:
|
||||
* - VerbExactMatchSignal (40%) - Explicit keywords and phrases
|
||||
* - VerbEmbeddingSignal (35%) - Neural similarity with verb embeddings
|
||||
* - VerbPatternSignal (20%) - Regex patterns and structures
|
||||
* - VerbContextSignal (5%) - Entity type pair hints
|
||||
* - VerbEmbeddingSignal (55%) - Neural similarity with verb embeddings
|
||||
* - VerbPatternSignal (30%) - Regex patterns and structures
|
||||
* - VerbContextSignal (15%) - Entity type pair hints
|
||||
*
|
||||
* Performance:
|
||||
* - Parallel signal execution (~15-20ms total)
|
||||
|
|
@ -24,11 +23,9 @@
|
|||
|
||||
import type { Brainy } from '../brainy.js'
|
||||
import type { VerbType, NounType } from '../types/graphTypes.js'
|
||||
import { VerbExactMatchSignal } from './signals/VerbExactMatchSignal.js'
|
||||
import { VerbEmbeddingSignal } from './signals/VerbEmbeddingSignal.js'
|
||||
import { VerbPatternSignal } from './signals/VerbPatternSignal.js'
|
||||
import { VerbContextSignal } from './signals/VerbContextSignal.js'
|
||||
import type { VerbSignal as ExactVerbSignal } from './signals/VerbExactMatchSignal.js'
|
||||
import type { VerbSignal as EmbeddingVerbSignal } from './signals/VerbEmbeddingSignal.js'
|
||||
import type { VerbSignal as PatternVerbSignal } from './signals/VerbPatternSignal.js'
|
||||
import type { VerbSignal as ContextVerbSignal } from './signals/VerbContextSignal.js'
|
||||
|
|
@ -40,7 +37,7 @@ export interface RelationshipExtractionResult {
|
|||
type: VerbType
|
||||
confidence: number
|
||||
weight: number
|
||||
source: 'ensemble' | 'exact-match' | 'pattern' | 'embedding' | 'context'
|
||||
source: 'ensemble' | 'pattern' | 'embedding' | 'context'
|
||||
evidence: string
|
||||
metadata?: {
|
||||
signalResults?: Array<{
|
||||
|
|
@ -61,10 +58,9 @@ export interface SmartRelationshipExtractorOptions {
|
|||
enableEnsemble?: boolean // Use ensemble vs single best signal (default: true)
|
||||
cacheSize?: number // LRU cache size (default: 2000)
|
||||
weights?: { // Custom signal weights (must sum to 1.0)
|
||||
exactMatch?: number // Default: 0.40
|
||||
embedding?: number // Default: 0.35
|
||||
pattern?: number // Default: 0.20
|
||||
context?: number // Default: 0.05
|
||||
embedding?: number // Default: 0.55
|
||||
pattern?: number // Default: 0.30
|
||||
context?: number // Default: 0.15
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -72,7 +68,7 @@ export interface SmartRelationshipExtractorOptions {
|
|||
* Internal signal result wrapper
|
||||
*/
|
||||
interface SignalResult {
|
||||
signal: 'exact-match' | 'embedding' | 'pattern' | 'context'
|
||||
signal: 'embedding' | 'pattern' | 'context'
|
||||
type: VerbType | null
|
||||
confidence: number
|
||||
weight: number
|
||||
|
|
@ -97,7 +93,6 @@ export class SmartRelationshipExtractor {
|
|||
private options: Required<Omit<SmartRelationshipExtractorOptions, 'weights'>> & { weights: Required<NonNullable<SmartRelationshipExtractorOptions['weights']>> }
|
||||
|
||||
// Signal instances
|
||||
private exactMatchSignal: VerbExactMatchSignal
|
||||
private embeddingSignal: VerbEmbeddingSignal
|
||||
private patternSignal: VerbPatternSignal
|
||||
private contextSignal: VerbContextSignal
|
||||
|
|
@ -110,7 +105,6 @@ export class SmartRelationshipExtractor {
|
|||
private stats = {
|
||||
calls: 0,
|
||||
cacheHits: 0,
|
||||
exactMatchWins: 0,
|
||||
embeddingWins: 0,
|
||||
patternWins: 0,
|
||||
contextWins: 0,
|
||||
|
|
@ -129,10 +123,9 @@ export class SmartRelationshipExtractor {
|
|||
enableEnsemble: options?.enableEnsemble ?? true,
|
||||
cacheSize: options?.cacheSize ?? 2000,
|
||||
weights: {
|
||||
exactMatch: options?.weights?.exactMatch ?? 0.40,
|
||||
embedding: options?.weights?.embedding ?? 0.35,
|
||||
pattern: options?.weights?.pattern ?? 0.20,
|
||||
context: options?.weights?.context ?? 0.05
|
||||
embedding: options?.weights?.embedding ?? 0.55,
|
||||
pattern: options?.weights?.pattern ?? 0.30,
|
||||
context: options?.weights?.context ?? 0.15
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -143,24 +136,19 @@ export class SmartRelationshipExtractor {
|
|||
}
|
||||
|
||||
// Initialize signals
|
||||
this.exactMatchSignal = new VerbExactMatchSignal(brain, {
|
||||
minConfidence: 0.50, // Lower threshold, ensemble will filter
|
||||
cacheSize: Math.floor(this.options.cacheSize / 4)
|
||||
})
|
||||
|
||||
this.embeddingSignal = new VerbEmbeddingSignal(brain, {
|
||||
minConfidence: 0.50,
|
||||
cacheSize: Math.floor(this.options.cacheSize / 4)
|
||||
cacheSize: Math.floor(this.options.cacheSize / 3)
|
||||
})
|
||||
|
||||
this.patternSignal = new VerbPatternSignal(brain, {
|
||||
minConfidence: 0.50,
|
||||
cacheSize: Math.floor(this.options.cacheSize / 4)
|
||||
cacheSize: Math.floor(this.options.cacheSize / 3)
|
||||
})
|
||||
|
||||
this.contextSignal = new VerbContextSignal(brain, {
|
||||
minConfidence: 0.50,
|
||||
cacheSize: Math.floor(this.options.cacheSize / 4)
|
||||
cacheSize: Math.floor(this.options.cacheSize / 3)
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -197,8 +185,7 @@ export class SmartRelationshipExtractor {
|
|||
|
||||
try {
|
||||
// Execute all signals in parallel
|
||||
const [exactMatch, embeddingMatch, patternMatch, contextMatch] = await Promise.all([
|
||||
this.exactMatchSignal.classify(context).catch(() => null),
|
||||
const [embeddingMatch, patternMatch, contextMatch] = await Promise.all([
|
||||
this.embeddingSignal.classify(context, options?.contextVector).catch(() => null),
|
||||
this.patternSignal.classify(subject, object, context).catch(() => null),
|
||||
this.contextSignal.classify(options?.subjectType, options?.objectType).catch(() => null)
|
||||
|
|
@ -206,13 +193,6 @@ export class SmartRelationshipExtractor {
|
|||
|
||||
// Wrap results with weights
|
||||
const signalResults: SignalResult[] = [
|
||||
{
|
||||
signal: 'exact-match',
|
||||
type: exactMatch?.type || null,
|
||||
confidence: exactMatch?.confidence || 0,
|
||||
weight: this.options.weights.exactMatch,
|
||||
evidence: exactMatch?.evidence || ''
|
||||
},
|
||||
{
|
||||
signal: 'embedding',
|
||||
type: embeddingMatch?.type || null,
|
||||
|
|
@ -368,7 +348,7 @@ export class SmartRelationshipExtractor {
|
|||
type: best.type!,
|
||||
confidence: best.confidence,
|
||||
weight: best.confidence,
|
||||
source: best.signal as any,
|
||||
source: best.signal,
|
||||
evidence: best.evidence,
|
||||
metadata: undefined
|
||||
}
|
||||
|
|
@ -381,8 +361,6 @@ export class SmartRelationshipExtractor {
|
|||
// Track win counts
|
||||
if (result.source === 'ensemble') {
|
||||
this.stats.ensembleWins++
|
||||
} else if (result.source === 'exact-match') {
|
||||
this.stats.exactMatchWins++
|
||||
} else if (result.source === 'embedding') {
|
||||
this.stats.embeddingWins++
|
||||
} else if (result.source === 'pattern') {
|
||||
|
|
@ -445,7 +423,6 @@ export class SmartRelationshipExtractor {
|
|||
cacheHitRate: this.stats.calls > 0 ? this.stats.cacheHits / this.stats.calls : 0,
|
||||
ensembleRate: this.stats.calls > 0 ? this.stats.ensembleWins / this.stats.calls : 0,
|
||||
signalStats: {
|
||||
exactMatch: this.exactMatchSignal.getStats(),
|
||||
embedding: this.embeddingSignal.getStats(),
|
||||
pattern: this.patternSignal.getStats(),
|
||||
context: this.contextSignal.getStats()
|
||||
|
|
@ -460,7 +437,6 @@ export class SmartRelationshipExtractor {
|
|||
this.stats = {
|
||||
calls: 0,
|
||||
cacheHits: 0,
|
||||
exactMatchWins: 0,
|
||||
embeddingWins: 0,
|
||||
patternWins: 0,
|
||||
contextWins: 0,
|
||||
|
|
@ -470,7 +446,6 @@ export class SmartRelationshipExtractor {
|
|||
averageSignalsUsed: 0
|
||||
}
|
||||
|
||||
this.exactMatchSignal.resetStats()
|
||||
this.embeddingSignal.resetStats()
|
||||
this.patternSignal.resetStats()
|
||||
this.contextSignal.resetStats()
|
||||
|
|
@ -483,7 +458,6 @@ export class SmartRelationshipExtractor {
|
|||
this.cache.clear()
|
||||
this.cacheOrder = []
|
||||
|
||||
this.exactMatchSignal.clearCache()
|
||||
this.embeddingSignal.clearCache()
|
||||
this.patternSignal.clearCache()
|
||||
this.contextSignal.clearCache()
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1,418 +0,0 @@
|
|||
/**
|
||||
* VerbExactMatchSignal - O(1) exact match relationship type classification
|
||||
*
|
||||
* HIGHEST WEIGHT: 40% (most reliable signal for verbs)
|
||||
*
|
||||
* Uses:
|
||||
* 1. O(1) keyword lookup (exact string match against 334 verb keywords)
|
||||
* 2. Context-aware matching (sentence patterns)
|
||||
* 3. Multi-word phrase matching ("created by", "part of", "belongs to")
|
||||
*
|
||||
* PRODUCTION-READY: No TODOs, no mocks, real implementation
|
||||
*/
|
||||
|
||||
import type { Brainy } from '../../brainy.js'
|
||||
import { VerbType } from '../../types/graphTypes.js'
|
||||
import { getKeywordEmbeddings, type KeywordEmbedding } from '../embeddedKeywordEmbeddings.js'
|
||||
|
||||
/**
|
||||
* Signal result with classification details
|
||||
*/
|
||||
export interface VerbSignal {
|
||||
type: VerbType
|
||||
confidence: number
|
||||
evidence: string
|
||||
metadata?: {
|
||||
matchedKeyword?: string
|
||||
matchPosition?: number
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for verb exact match signal
|
||||
*/
|
||||
export interface VerbExactMatchSignalOptions {
|
||||
minConfidence?: number // Minimum confidence threshold (default: 0.70)
|
||||
cacheSize?: number // LRU cache size (default: 2000)
|
||||
caseSensitive?: boolean // Case-sensitive matching (default: false)
|
||||
}
|
||||
|
||||
/**
|
||||
* VerbExactMatchSignal - Instant O(1) relationship type classification
|
||||
*
|
||||
* Production features:
|
||||
* - O(1) hash table lookups using 334 pre-computed verb keywords
|
||||
* - Multi-word phrase matching ("created by", "part of", etc.)
|
||||
* - Context-aware pattern detection
|
||||
* - LRU cache for hot paths
|
||||
* - High confidence (0.85-0.95) - most reliable signal
|
||||
*/
|
||||
export class VerbExactMatchSignal {
|
||||
private brain: Brainy
|
||||
private options: Required<VerbExactMatchSignalOptions>
|
||||
|
||||
// O(1) keyword lookup (key: normalized keyword → value: VerbType + confidence)
|
||||
private keywordIndex: Map<string, { type: VerbType; confidence: number; isCanonical: boolean }> = new Map()
|
||||
|
||||
// LRU cache
|
||||
private cache: Map<string, VerbSignal | null> = new Map()
|
||||
private cacheOrder: string[] = []
|
||||
|
||||
// Statistics
|
||||
private stats = {
|
||||
calls: 0,
|
||||
cacheHits: 0,
|
||||
exactMatches: 0,
|
||||
phraseMatches: 0,
|
||||
partialMatches: 0
|
||||
}
|
||||
|
||||
constructor(brain: Brainy, options?: VerbExactMatchSignalOptions) {
|
||||
this.brain = brain
|
||||
this.options = {
|
||||
minConfidence: options?.minConfidence ?? 0.70,
|
||||
cacheSize: options?.cacheSize ?? 2000,
|
||||
caseSensitive: options?.caseSensitive ?? false
|
||||
}
|
||||
|
||||
// Build keyword index from pre-computed embeddings
|
||||
this.buildKeywordIndex()
|
||||
}
|
||||
|
||||
/**
|
||||
* Build keyword index from embedded keyword embeddings (O(n) once at startup)
|
||||
*/
|
||||
private buildKeywordIndex(): void {
|
||||
const allKeywords = getKeywordEmbeddings()
|
||||
|
||||
// Filter to verb keywords only
|
||||
const verbKeywords = allKeywords.filter(k => k.typeCategory === 'verb')
|
||||
|
||||
for (const keyword of verbKeywords) {
|
||||
const normalized = this.normalize(keyword.keyword)
|
||||
|
||||
// Only keep highest confidence for duplicate keywords
|
||||
const existing = this.keywordIndex.get(normalized)
|
||||
if (!existing || keyword.confidence > existing.confidence) {
|
||||
this.keywordIndex.set(normalized, {
|
||||
type: keyword.type as VerbType,
|
||||
confidence: keyword.confidence,
|
||||
isCanonical: keyword.isCanonical
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Verify we have the expected number of verb keywords
|
||||
if (this.keywordIndex.size === 0) {
|
||||
throw new Error('VerbExactMatchSignal: No verb keywords found in embeddings')
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Classify relationship type from context text
|
||||
*
|
||||
* @param context Full context text (sentence or paragraph)
|
||||
* @returns VerbSignal with classified type or null
|
||||
*/
|
||||
async classify(context: string): Promise<VerbSignal | null> {
|
||||
this.stats.calls++
|
||||
|
||||
if (!context || context.trim().length === 0) {
|
||||
return null
|
||||
}
|
||||
|
||||
// Check cache
|
||||
const cacheKey = this.getCacheKey(context)
|
||||
const cached = this.getFromCache(cacheKey)
|
||||
if (cached !== undefined) {
|
||||
this.stats.cacheHits++
|
||||
return cached
|
||||
}
|
||||
|
||||
try {
|
||||
const result = this.classifyInternal(context)
|
||||
|
||||
// Add to cache
|
||||
this.addToCache(cacheKey, result)
|
||||
|
||||
return result
|
||||
} catch (error) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal classification logic (not cached)
|
||||
*/
|
||||
private classifyInternal(context: string): VerbSignal | null {
|
||||
const normalized = this.normalize(context)
|
||||
|
||||
// Strategy 1: Multi-word phrase matching (highest priority)
|
||||
// Look for common verb phrases: "created by", "part of", "belongs to", etc.
|
||||
const phraseResult = this.matchPhrases(normalized)
|
||||
if (phraseResult && phraseResult.confidence >= this.options.minConfidence) {
|
||||
this.stats.phraseMatches++
|
||||
return phraseResult
|
||||
}
|
||||
|
||||
// Strategy 2: Single keyword matching
|
||||
// Split into tokens and check each against keyword index
|
||||
const tokens = this.tokenize(normalized)
|
||||
|
||||
let bestMatch: VerbSignal | null = null
|
||||
let bestConfidence = 0
|
||||
|
||||
for (let i = 0; i < tokens.length; i++) {
|
||||
const token = tokens[i]
|
||||
|
||||
// Check exact keyword match
|
||||
const match = this.keywordIndex.get(token)
|
||||
if (match) {
|
||||
const confidence = match.isCanonical ? 0.95 : 0.85
|
||||
|
||||
if (confidence > bestConfidence) {
|
||||
bestConfidence = confidence
|
||||
bestMatch = {
|
||||
type: match.type,
|
||||
confidence,
|
||||
evidence: `Exact keyword match: "${token}"`,
|
||||
metadata: {
|
||||
matchedKeyword: token,
|
||||
matchPosition: i
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check bi-gram (two consecutive tokens)
|
||||
if (i < tokens.length - 1) {
|
||||
const bigram = `${tokens[i]} ${tokens[i + 1]}`
|
||||
const bigramMatch = this.keywordIndex.get(bigram)
|
||||
|
||||
if (bigramMatch) {
|
||||
const confidence = bigramMatch.isCanonical ? 0.95 : 0.85
|
||||
|
||||
if (confidence > bestConfidence) {
|
||||
bestConfidence = confidence
|
||||
bestMatch = {
|
||||
type: bigramMatch.type,
|
||||
confidence,
|
||||
evidence: `Phrase match: "${bigram}"`,
|
||||
metadata: {
|
||||
matchedKeyword: bigram,
|
||||
matchPosition: i
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check tri-gram (three consecutive tokens)
|
||||
if (i < tokens.length - 2) {
|
||||
const trigram = `${tokens[i]} ${tokens[i + 1]} ${tokens[i + 2]}`
|
||||
const trigramMatch = this.keywordIndex.get(trigram)
|
||||
|
||||
if (trigramMatch) {
|
||||
const confidence = trigramMatch.isCanonical ? 0.95 : 0.85
|
||||
|
||||
if (confidence > bestConfidence) {
|
||||
bestConfidence = confidence
|
||||
bestMatch = {
|
||||
type: trigramMatch.type,
|
||||
confidence,
|
||||
evidence: `Phrase match: "${trigram}"`,
|
||||
metadata: {
|
||||
matchedKeyword: trigram,
|
||||
matchPosition: i
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (bestMatch && bestMatch.confidence >= this.options.minConfidence) {
|
||||
this.stats.exactMatches++
|
||||
return bestMatch
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Match common multi-word verb phrases
|
||||
*
|
||||
* These are high-confidence patterns that indicate specific relationships
|
||||
*/
|
||||
private matchPhrases(text: string): VerbSignal | null {
|
||||
// Common relationship phrases with their VerbTypes
|
||||
const phrases: Array<{ pattern: RegExp; type: VerbType; confidence: number }> = [
|
||||
// Creation relationships
|
||||
{ pattern: /created?\s+by/i, type: VerbType.Creates, confidence: 0.95 },
|
||||
{ pattern: /authored?\s+by/i, type: VerbType.Creates, confidence: 0.95 },
|
||||
{ pattern: /written\s+by/i, type: VerbType.Creates, confidence: 0.95 },
|
||||
{ pattern: /developed\s+by/i, type: VerbType.Creates, confidence: 0.90 },
|
||||
{ pattern: /built\s+by/i, type: VerbType.Creates, confidence: 0.85 },
|
||||
|
||||
// Ownership relationships
|
||||
{ pattern: /owned\s+by/i, type: VerbType.Owns, confidence: 0.95 },
|
||||
{ pattern: /belongs\s+to/i, type: VerbType.Owns, confidence: 0.95 },
|
||||
{ pattern: /attributed\s+to/i, type: VerbType.AttributedTo, confidence: 0.95 },
|
||||
|
||||
// Part/Whole relationships
|
||||
{ pattern: /part\s+of/i, type: VerbType.PartOf, confidence: 0.95 },
|
||||
{ pattern: /contains/i, type: VerbType.Contains, confidence: 0.90 },
|
||||
{ pattern: /includes/i, type: VerbType.Contains, confidence: 0.85 },
|
||||
|
||||
// Location relationships
|
||||
{ pattern: /located\s+(?:at|in)/i, type: VerbType.LocatedAt, confidence: 0.95 },
|
||||
{ pattern: /based\s+in/i, type: VerbType.LocatedAt, confidence: 0.90 },
|
||||
{ pattern: /situated\s+in/i, type: VerbType.LocatedAt, confidence: 0.90 },
|
||||
|
||||
// Membership relationships
|
||||
{ pattern: /member\s+of/i, type: VerbType.MemberOf, confidence: 0.95 },
|
||||
{ pattern: /works?\s+(?:at|for)/i, type: VerbType.WorksWith, confidence: 0.85 },
|
||||
{ pattern: /employed\s+by/i, type: VerbType.WorksWith, confidence: 0.90 },
|
||||
|
||||
// Reporting relationships
|
||||
{ pattern: /reports?\s+to/i, type: VerbType.ReportsTo, confidence: 0.95 },
|
||||
{ pattern: /manages/i, type: VerbType.ReportsTo, confidence: 0.85 },
|
||||
{ pattern: /supervises/i, type: VerbType.ReportsTo, confidence: 0.95 },
|
||||
|
||||
// Reference relationships
|
||||
{ pattern: /references/i, type: VerbType.References, confidence: 0.90 },
|
||||
{ pattern: /cites/i, type: VerbType.References, confidence: 0.90 },
|
||||
{ pattern: /mentions/i, type: VerbType.References, confidence: 0.85 },
|
||||
|
||||
// Temporal relationships
|
||||
{ pattern: /precedes/i, type: VerbType.Precedes, confidence: 0.90 },
|
||||
{ pattern: /follows/i, type: VerbType.Precedes, confidence: 0.90 },
|
||||
{ pattern: /before/i, type: VerbType.Precedes, confidence: 0.75 },
|
||||
{ pattern: /after/i, type: VerbType.Precedes, confidence: 0.75 },
|
||||
|
||||
// Causal relationships
|
||||
{ pattern: /causes/i, type: VerbType.Causes, confidence: 0.90 },
|
||||
{ pattern: /requires/i, type: VerbType.Requires, confidence: 0.90 },
|
||||
{ pattern: /depends\s+on/i, type: VerbType.DependsOn, confidence: 0.95 },
|
||||
|
||||
// Transformation relationships
|
||||
{ pattern: /transforms/i, type: VerbType.Transforms, confidence: 0.90 },
|
||||
{ pattern: /modifies/i, type: VerbType.Modifies, confidence: 0.90 },
|
||||
{ pattern: /becomes/i, type: VerbType.Becomes, confidence: 0.90 }
|
||||
]
|
||||
|
||||
for (const { pattern, type, confidence } of phrases) {
|
||||
if (pattern.test(text)) {
|
||||
return {
|
||||
type,
|
||||
confidence,
|
||||
evidence: `Phrase pattern match: ${pattern.source}`,
|
||||
metadata: {
|
||||
matchedKeyword: pattern.source
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize text for matching
|
||||
*/
|
||||
private normalize(text: string): string {
|
||||
let normalized = text.trim()
|
||||
|
||||
if (!this.options.caseSensitive) {
|
||||
normalized = normalized.toLowerCase()
|
||||
}
|
||||
|
||||
// Remove extra whitespace
|
||||
normalized = normalized.replace(/\s+/g, ' ')
|
||||
|
||||
return normalized
|
||||
}
|
||||
|
||||
/**
|
||||
* Tokenize text into words
|
||||
*/
|
||||
private tokenize(text: string): string[] {
|
||||
return text
|
||||
.split(/\s+/)
|
||||
.map(token => token.replace(/[^\w\s-]/g, '')) // Remove punctuation except hyphens
|
||||
.filter(token => token.length > 0)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get cache key
|
||||
*/
|
||||
private getCacheKey(context: string): string {
|
||||
return this.normalize(context).substring(0, 200) // Limit key length
|
||||
}
|
||||
|
||||
/**
|
||||
* Get from LRU cache
|
||||
*/
|
||||
private getFromCache(key: string): VerbSignal | null | undefined {
|
||||
if (!this.cache.has(key)) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
const cached = this.cache.get(key)
|
||||
|
||||
// Move to end (most recently used)
|
||||
this.cacheOrder = this.cacheOrder.filter(k => k !== key)
|
||||
this.cacheOrder.push(key)
|
||||
|
||||
return cached ?? null
|
||||
}
|
||||
|
||||
/**
|
||||
* Add to LRU cache with eviction
|
||||
*/
|
||||
private addToCache(key: string, value: VerbSignal | null): void {
|
||||
this.cache.set(key, value)
|
||||
this.cacheOrder.push(key)
|
||||
|
||||
// Evict oldest if over limit
|
||||
if (this.cache.size > this.options.cacheSize) {
|
||||
const oldest = this.cacheOrder.shift()
|
||||
if (oldest) {
|
||||
this.cache.delete(oldest)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get statistics
|
||||
*/
|
||||
getStats() {
|
||||
return {
|
||||
...this.stats,
|
||||
keywordCount: this.keywordIndex.size,
|
||||
cacheSize: this.cache.size,
|
||||
cacheHitRate: this.stats.calls > 0 ? this.stats.cacheHits / this.stats.calls : 0
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset statistics
|
||||
*/
|
||||
resetStats(): void {
|
||||
this.stats = {
|
||||
calls: 0,
|
||||
cacheHits: 0,
|
||||
exactMatches: 0,
|
||||
phraseMatches: 0,
|
||||
partialMatches: 0
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear cache
|
||||
*/
|
||||
clearCache(): void {
|
||||
this.cache.clear()
|
||||
this.cacheOrder = []
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue