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:
David Snelling 2026-01-06 12:52:34 -08:00
parent 81cd16e41b
commit da7d2ed29d
60 changed files with 3887 additions and 448557 deletions

View file

@ -1,378 +0,0 @@
/**
* TypeInference Hybrid System Tests - Vector Fallback Integration
*
* Tests for hybrid type inference combining keyword matching (fast path)
* with vector similarity fallback (intelligent fallback for unknown words)
*/
import { describe, it, expect, beforeEach } from 'vitest'
import { TypeInferenceSystem } from '../../src/query/typeInference.js'
import { NounType } from '../../src/types/graphTypes.js'
describe('TypeInference Hybrid System', () => {
// ========== Fast Path Tests (No Fallback) ==========
describe('Fast Path - Keyword Matching Only', () => {
let system: TypeInferenceSystem
beforeEach(() => {
// Default config: vector fallback disabled
system = new TypeInferenceSystem()
})
it('should use fast path for known keywords', async () => {
const start = performance.now()
const results = await system.inferTypesAsync('Find engineers in San Francisco')
const elapsed = performance.now() - start
// Should be fast even with async
expect(elapsed).toBeLessThan(10)
expect(results.length).toBeGreaterThan(0)
expect(results[0].type).toBe(NounType.Person)
})
it('should return empty array for unknown words (no fallback)', () => {
const results = system.inferTypes('Find xyzphysicians')
expect(results).toEqual([])
})
it('should handle typos without fallback by returning empty', () => {
const results = system.inferTypes('Find documnets')
// Without fallback, typos are not handled
// May or may not match depending on partial matches
})
})
// ========== Hybrid Mode Tests (With Fallback) ==========
describe('Hybrid Mode - Keyword + Vector Fallback', () => {
let system: TypeInferenceSystem
beforeEach(() => {
// Enable vector fallback
system = new TypeInferenceSystem({
enableVectorFallback: true,
fallbackConfidenceThreshold: 0.7,
vectorThreshold: 0.5,
debug: false
})
})
it('should still use fast path for high-confidence keyword matches', async () => {
const start = performance.now()
const results = await system.inferTypesAsync('Find engineers in San Francisco')
const elapsed = performance.now() - start
// High confidence keywords should NOT trigger fallback
expect(elapsed).toBeLessThan(10)
expect(results.length).toBeGreaterThan(0)
expect(results[0].confidence).toBeGreaterThanOrEqual(0.7)
})
it('should trigger vector fallback for completely unknown words', async () => {
const results = await system.inferTypesAsync('Find xyzabc qwerty')
// Vector fallback may or may not find matches for gibberish
expect(Array.isArray(results)).toBe(true)
})
it('should trigger fallback for low confidence matches', async () => {
const results = await system.inferTypesAsync('Find obscure technical jargon')
expect(Array.isArray(results)).toBe(true)
})
it('should handle typos with vector similarity fallback', async () => {
const results = await system.inferTypesAsync('Find documnets')
// Vector similarity should handle typos semantically
expect(results.length).toBeGreaterThanOrEqual(0)
// May find Document type via semantic similarity
const docType = results.find(r => r.type === NounType.Document)
if (docType) {
expect(docType.confidence).toBeGreaterThan(0)
}
})
it('should mark vector results with special keyword marker', async () => {
const results = await system.inferTypesAsync('xyzabc')
// Vector results should have <vector-similarity> marker
const vectorResults = results.filter(r =>
r.matchedKeywords.includes('<vector-similarity>')
)
expect(vectorResults.length).toBeGreaterThanOrEqual(0)
})
})
// ========== Configuration Tests ==========
describe('Configuration Options', () => {
it('should respect fallbackConfidenceThreshold', async () => {
// Very high threshold - triggers fallback even for good matches
const system = new TypeInferenceSystem({
enableVectorFallback: true,
fallbackConfidenceThreshold: 0.95,
debug: false
})
const results = system.inferTypesAsync('engineer')
// Even "engineer" (0.9 confidence) should trigger fallback with 0.95 threshold
if (results instanceof Promise) {
const resolved = await results
expect(Array.isArray(resolved)).toBe(true)
}
})
it('should respect vectorThreshold for filtering results', async () => {
// Very high vector threshold - filters weak matches
const system = new TypeInferenceSystem({
enableVectorFallback: true,
fallbackConfidenceThreshold: 0.7,
vectorThreshold: 0.9, // Very high threshold
debug: false
})
const results = system.inferTypesAsync('xyzabc')
if (results instanceof Promise) {
const resolved = await results
// High threshold should filter out weak vector matches
expect(resolved.every(r => r.confidence >= 0.9 || !r.matchedKeywords.includes('<vector-similarity>'))).toBe(true)
}
})
it('should not trigger fallback when disabled', () => {
const system = new TypeInferenceSystem({
enableVectorFallback: false
})
const results = system.inferTypesAsync('xyzabc unknown words')
// Should return synchronously even with no matches
expect(results).not.toBeInstanceOf(Promise)
expect(results).toEqual([])
})
})
// ========== Result Merging Tests ==========
describe('Result Merging', () => {
let system: TypeInferenceSystem
beforeEach(() => {
system = new TypeInferenceSystem({
enableVectorFallback: true,
fallbackConfidenceThreshold: 0.5, // Low threshold to test merging
vectorThreshold: 0.4,
debug: false
})
})
it('should merge keyword and vector results without duplicates', async () => {
const results = system.inferTypesAsync('engineer')
if (results instanceof Promise) {
const resolved = await results
// Should not have duplicate types
const types = resolved.map(r => r.type)
const uniqueTypes = new Set(types)
expect(types.length).toBe(uniqueTypes.size)
}
})
it('should prioritize keyword matches over vector matches', async () => {
const results = system.inferTypesAsync('software engineer')
if (results instanceof Promise) {
const resolved = await results
// Keyword matches should have higher confidence than vector-only matches
const keywordResults = resolved.filter(
r => !r.matchedKeywords.includes('<vector-similarity>')
)
const vectorResults = resolved.filter(
r => r.matchedKeywords.includes('<vector-similarity>')
)
if (keywordResults.length > 0 && vectorResults.length > 0) {
expect(keywordResults[0].confidence).toBeGreaterThanOrEqual(
vectorResults[0].confidence
)
}
}
})
it('should boost keyword confidence in merged results', async () => {
const keywordOnlySystem = new TypeInferenceSystem({
enableVectorFallback: false
})
const hybridSystem = new TypeInferenceSystem({
enableVectorFallback: true,
fallbackConfidenceThreshold: 0.3, // Very low to trigger merging
debug: false
})
const keywordResults = keywordOnlySystem.inferTypes('engineer')
const hybridResults = hybridSystem.inferTypes('engineer')
if (hybridResults instanceof Promise) {
const resolved = await hybridResults
// Hybrid system should boost keyword confidence (20% boost)
const keywordType = (keywordResults as any).find(
(r: any) => r.type === NounType.Person
)
const hybridType = resolved.find(r => r.type === NounType.Person)
if (keywordType && hybridType && !hybridType.matchedKeywords.includes('<vector-similarity>')) {
expect(hybridType.confidence).toBeGreaterThanOrEqual(keywordType.confidence)
}
}
})
})
// ========== Performance Tests ==========
describe('Performance Characteristics', () => {
it('should complete fast path in < 5ms', () => {
const system = new TypeInferenceSystem({
enableVectorFallback: true
})
const start = performance.now()
const results = system.inferTypesAsync('Find engineers at Tesla')
const elapsed = performance.now() - start
// Fast path should still be fast even with fallback enabled
expect(results).not.toBeInstanceOf(Promise)
expect(elapsed).toBeLessThan(5)
})
it('should complete vector fallback in reasonable time (< 200ms)', async () => {
const system = new TypeInferenceSystem({
enableVectorFallback: true,
fallbackConfidenceThreshold: 0.7,
debug: false
})
const start = performance.now()
const results = system.inferTypesAsync('xyzabc qwerty')
if (results instanceof Promise) {
await results
const elapsed = performance.now() - start
// Vector fallback should complete in reasonable time
// Note: First call includes model loading, subsequent calls are faster
expect(elapsed).toBeLessThan(10000) // 10 seconds max for first call (model loading)
}
}, 15000) // 15 second timeout for this test
})
// ========== Real-World Scenarios ==========
describe('Real-World Usage Scenarios', () => {
let system: TypeInferenceSystem
beforeEach(() => {
system = new TypeInferenceSystem({
enableVectorFallback: true,
fallbackConfidenceThreshold: 0.7,
vectorThreshold: 0.5,
debug: false
})
})
it('should handle medical terminology with fallback', async () => {
const results = system.inferTypesAsync('Find cardiologists')
// "cardiologists" may not be in keyword list, but vector should understand it's a Person
if (results instanceof Promise) {
const resolved = await results
const personType = resolved.find(r => r.type === NounType.Person)
if (personType) {
expect(personType.confidence).toBeGreaterThan(0.5)
}
}
})
it('should handle technical abbreviations with fallback', async () => {
const results = system.inferTypesAsync('Find SRE')
// "SRE" (Site Reliability Engineer) may not be in keyword list
if (results instanceof Promise) {
const resolved = await results
// Vector fallback may understand this is related to Person/Role
expect(resolved.length).toBeGreaterThanOrEqual(0)
}
})
it('should handle misspelled common words', async () => {
const results = system.inferTypesAsync('Find companys in NYC')
// "companys" is misspelled, but vector should understand
if (results instanceof Promise) {
const resolved = await results
const orgType = resolved.find(r => r.type === NounType.Organization)
if (orgType) {
expect(orgType.confidence).toBeGreaterThan(0)
}
}
})
it('should handle domain-specific jargon', async () => {
const results = system.inferTypesAsync('Find ML practitioners')
// "practitioners" may not map directly to Person in keywords
if (results instanceof Promise) {
const resolved = await results
const personType = resolved.find(r => r.type === NounType.Person)
if (personType) {
expect(personType.confidence).toBeGreaterThan(0)
}
}
})
})
// ========== Statistics Tests ==========
describe('System Statistics', () => {
it('should report vector fallback in stats when enabled', () => {
const system = new TypeInferenceSystem({
enableVectorFallback: true
})
const stats = system.getStats()
expect(stats.config.enableVectorFallback).toBe(true)
expect(stats.config.fallbackConfidenceThreshold).toBeDefined()
expect(stats.config.vectorThreshold).toBeDefined()
})
it('should report correct config values', () => {
const system = new TypeInferenceSystem({
enableVectorFallback: true,
fallbackConfidenceThreshold: 0.8,
vectorThreshold: 0.6
})
const stats = system.getStats()
expect(stats.config.fallbackConfidenceThreshold).toBe(0.8)
expect(stats.config.vectorThreshold).toBe(0.6)
})
})
})

View file

@ -1,285 +0,0 @@
/**
* TypeInference System Tests - Phase 3
*
* Comprehensive tests for type inference from natural language queries
* Target: 15 tests covering all inference scenarios
*/
import { describe, it, expect, beforeEach } from 'vitest'
import { TypeInferenceSystem, inferTypes } from '../../src/query/typeInference.js'
import { NounType } from '../../src/types/graphTypes.js'
describe('TypeInference System', () => {
let inferenceSystem: TypeInferenceSystem
beforeEach(() => {
inferenceSystem = new TypeInferenceSystem()
})
// ========== Exact Match Tests (5 tests) ==========
describe('Exact Keyword Matching', () => {
it('should infer Person from "engineer"', () => {
const results = inferenceSystem.inferTypes('Find engineers')
expect(results.length).toBeGreaterThan(0)
expect(results[0].type).toBe(NounType.Person)
expect(results[0].confidence).toBeGreaterThanOrEqual(0.8)
expect(results[0].matchedKeywords).toContain('engineers')
})
it('should infer Location from "San Francisco"', () => {
const results = inferenceSystem.inferTypes('people in San Francisco')
expect(results).toContainEqual(
expect.objectContaining({
type: NounType.Location,
confidence: expect.any(Number)
})
)
const locationResult = results.find(r => r.type === NounType.Location)
expect(locationResult?.confidence).toBeGreaterThanOrEqual(0.8)
})
it('should infer Document from "report"', () => {
const results = inferenceSystem.inferTypes('show me the latest reports')
expect(results.length).toBeGreaterThan(0)
expect(results[0].type).toBe(NounType.Document)
expect(results[0].confidence).toBeGreaterThanOrEqual(0.8)
})
it('should infer Organization from "company"', () => {
const results = inferenceSystem.inferTypes('find tech companies')
expect(results).toContainEqual(
expect.objectContaining({
type: NounType.Organization,
confidence: expect.any(Number)
})
)
const orgResult = results.find(r => r.type === NounType.Organization)
expect(orgResult?.confidence).toBeGreaterThanOrEqual(0.8)
})
it('should infer Concept from "artificial intelligence"', () => {
const results = inferenceSystem.inferTypes(
'documents about artificial intelligence'
)
expect(results).toContainEqual(
expect.objectContaining({
type: NounType.Concept
})
)
const conceptResult = results.find(r => r.type === NounType.Concept)
expect(conceptResult).toBeDefined()
expect(conceptResult?.confidence).toBeGreaterThanOrEqual(0.7)
})
})
// ========== Multi-Type Tests (3 tests) ==========
describe('Multi-Type Inference', () => {
it('should infer [Person, Organization] from "employees at Tesla"', () => {
const results = inferenceSystem.inferTypes('find employees at Tesla')
expect(results.length).toBeGreaterThanOrEqual(2)
const types = results.map(r => r.type)
expect(types).toContain(NounType.Person)
expect(types).toContain(NounType.Organization)
})
it('should infer [Document, Concept] from "papers about quantum computing"', () => {
const results = inferenceSystem.inferTypes(
'show papers about quantum computing'
)
expect(results.length).toBeGreaterThanOrEqual(2)
const types = results.map(r => r.type)
expect(types).toContain(NounType.Document)
expect(types).toContain(NounType.Concept)
})
it('should infer [Event, Location] from "conferences in NYC"', () => {
const results = inferenceSystem.inferTypes(
'upcoming conferences in New York City'
)
expect(results.length).toBeGreaterThanOrEqual(2)
const types = results.map(r => r.type)
expect(types).toContain(NounType.Event)
expect(types).toContain(NounType.Location)
})
})
// ========== Confidence Tests (3 tests) ==========
describe('Confidence Scoring', () => {
it('should return high confidence for exact matches', () => {
const results = inferenceSystem.inferTypes('software engineer')
expect(results.length).toBeGreaterThan(0)
expect(results[0].confidence).toBeGreaterThanOrEqual(0.9)
})
it('should return moderate confidence for partial matches', () => {
const results = inferenceSystem.inferTypes('engineering team member')
expect(results.length).toBeGreaterThan(0)
// Should have multiple types matched (team → Organization, member → User, engineering → Concept)
const types = results.map(r => r.type)
expect(types.length).toBeGreaterThanOrEqual(2)
// Should have Organization and User types
expect(types).toContain(NounType.Organization)
expect(types).toContain(NounType.Person)
})
it('should boost confidence for multiple keyword matches', () => {
const singleKeyword = inferenceSystem.inferTypes('engineer')
const multiKeyword = inferenceSystem.inferTypes('software engineer developer')
expect(singleKeyword[0].confidence).toBeLessThan(
multiKeyword[0].confidence
)
})
})
// ========== Edge Cases (4 tests) ==========
describe('Edge Cases', () => {
it('should handle empty query', () => {
const results = inferenceSystem.inferTypes('')
expect(results).toEqual([])
})
it('should handle single-word query', () => {
const results = inferenceSystem.inferTypes('documents')
expect(results.length).toBeGreaterThan(0)
expect(results[0].type).toBe(NounType.Document)
})
it('should handle very long query (100+ words)', () => {
const longQuery =
'Find all software engineers and developers working at technology companies ' +
'in San Francisco and New York who have experience with artificial intelligence ' +
'machine learning deep learning natural language processing computer vision ' +
'data science analytics big data distributed systems cloud computing ' +
'microservices kubernetes docker containers orchestration deployment ' +
'continuous integration continuous deployment devops site reliability ' +
'engineering infrastructure automation monitoring observability logging ' +
'tracing metrics dashboards alerting incident response on call rotation'
const results = inferenceSystem.inferTypes(longQuery)
expect(results.length).toBeGreaterThan(0)
expect(results[0].type).toBeDefined()
// Should have Person, Organization, Location, Concept types
const types = results.map(r => r.type)
expect(types).toContain(NounType.Person)
expect(types).toContain(NounType.Organization)
expect(types).toContain(NounType.Location)
expect(types).toContain(NounType.Concept)
})
it('should handle queries with no keyword matches', () => {
const results = inferenceSystem.inferTypes('xyzabc qwerty asdfgh')
expect(results).toEqual([])
})
})
// ========== Performance Tests (2 tests) ==========
describe('Performance', () => {
it('should infer types in < 5ms', () => {
const start = performance.now()
inferenceSystem.inferTypes('Find software engineers in San Francisco')
const elapsed = performance.now() - start
expect(elapsed).toBeLessThan(5) // Target: < 1ms, allow 5ms for CI
})
it('should handle 100 sequential queries efficiently', () => {
const queries = [
'Find engineers',
'Show documents',
'List companies',
'Find events',
'Show reports'
]
const start = performance.now()
for (let i = 0; i < 100; i++) {
inferenceSystem.inferTypes(queries[i % queries.length])
}
const elapsed = performance.now() - start
const avgTime = elapsed / 100
expect(avgTime).toBeLessThan(5) // < 5ms average per query
})
})
// ========== Configuration Tests (2 tests) ==========
describe('Configuration', () => {
it('should respect minConfidence threshold', () => {
const system = new TypeInferenceSystem({ minConfidence: 0.9 })
const results = system.inferTypes('maybe a document or file')
// High threshold should filter out low-confidence matches
expect(results.every(r => r.confidence >= 0.9)).toBe(true)
})
it('should respect maxTypes limit', () => {
const system = new TypeInferenceSystem({ maxTypes: 2 })
const results = system.inferTypes(
'Find engineers at companies in cities working on projects'
)
expect(results.length).toBeLessThanOrEqual(2)
})
})
// ========== Convenience Functions (1 test) ==========
describe('Global Convenience Functions', () => {
it('should provide inferTypes() convenience function', () => {
const results = inferTypes('Find engineers')
expect(results.length).toBeGreaterThan(0)
expect(results[0].type).toBe(NounType.Person)
})
})
// ========== Statistics (1 test) ==========
describe('System Statistics', () => {
it('should provide keyword and phrase statistics', () => {
const stats = inferenceSystem.getStats()
expect(stats.keywordCount).toBeGreaterThan(500)
expect(stats.phraseCount).toBeGreaterThan(30)
expect(stats.config).toBeDefined()
expect(stats.config.minConfidence).toBe(0.4)
expect(stats.config.maxTypes).toBe(5)
})
})
})

View file

@ -1,8 +1,8 @@
/**
* WASM Embedding Integration Test
*
* Tests the actual WASM embedding engine with real model inference.
* NO mocks - this loads the real ONNX model and generates real embeddings.
* Tests the Candle WASM embedding engine with real model inference.
* NO mocks - this loads the real embedded model and generates real embeddings.
*/
import { describe, it, expect, beforeAll } from 'vitest'

View file

@ -1,248 +0,0 @@
/**
* TypeAwareQueryPlanner Tests - Phase 3
*
* Comprehensive tests for query planning and routing strategy selection
* Target: 10 tests covering all planning scenarios
*/
import { describe, it, expect, beforeEach } from 'vitest'
import {
TypeAwareQueryPlanner,
planQuery,
getQueryPlanner
} from '../src/query/typeAwareQueryPlanner.js'
import { TypeInferenceSystem } from '../src/query/typeInference.js'
import { NounType } from '../src/types/graphTypes.js'
describe('TypeAwareQueryPlanner', () => {
let planner: TypeAwareQueryPlanner
beforeEach(() => {
planner = new TypeAwareQueryPlanner()
})
// ========== Routing Tests (4 tests) ==========
describe('Routing Strategy Selection', () => {
it('should use single-type routing for high confidence queries', () => {
const plan = planner.planQuery('Find engineers')
expect(plan.routing).toBe('single-type')
expect(plan.targetTypes.length).toBe(1)
expect(plan.targetTypes[0]).toBe(NounType.Person)
expect(plan.confidence).toBeGreaterThanOrEqual(0.8)
expect(plan.estimatedSpeedup).toBeGreaterThan(10) // 42/1 types
})
it('should use multi-type routing for multiple high-confidence types', () => {
const plan = planner.planQuery('employees at tech companies')
expect(plan.routing).toBe('multi-type')
expect(plan.targetTypes.length).toBeGreaterThanOrEqual(2)
expect(plan.targetTypes.length).toBeLessThanOrEqual(5)
expect(plan.targetTypes).toContain(NounType.Person)
expect(plan.targetTypes).toContain(NounType.Organization)
expect(plan.estimatedSpeedup).toBeGreaterThan(1)
expect(plan.estimatedSpeedup).toBeLessThanOrEqual(42)
})
it('should use all-types routing for low confidence queries', () => {
const plan = planner.planQuery('show me stuff')
expect(plan.routing).toBe('all-types')
expect(plan.targetTypes.length).toBe(42) // All noun types
expect(plan.estimatedSpeedup).toBe(1.0) // No speedup
expect(plan.confidence).toBeLessThan(0.6)
})
it('should use all-types routing for empty queries', () => {
const plan = planner.planQuery('')
expect(plan.routing).toBe('all-types')
expect(plan.targetTypes.length).toBe(42)
expect(plan.estimatedSpeedup).toBe(1.0)
expect(plan.reasoning).toContain('Empty query')
})
})
// ========== Plan Generation Tests (3 tests) ==========
describe('Query Plan Generation', () => {
it('should generate complete query plan with all fields', () => {
const plan = planner.planQuery('Find software engineers')
expect(plan).toHaveProperty('originalQuery')
expect(plan).toHaveProperty('inferredTypes')
expect(plan).toHaveProperty('routing')
expect(plan).toHaveProperty('targetTypes')
expect(plan).toHaveProperty('estimatedSpeedup')
expect(plan).toHaveProperty('confidence')
expect(plan).toHaveProperty('reasoning')
expect(plan.originalQuery).toBe('Find software engineers')
expect(Array.isArray(plan.inferredTypes)).toBe(true)
expect(Array.isArray(plan.targetTypes)).toBe(true)
})
it('should calculate accurate speedup estimates', () => {
const singleType = planner.planQuery('Find engineers')
const multiType = planner.planQuery('engineers at companies')
const allTypes = planner.planQuery('show everything')
// Single-type: 42/1 = 42x
expect(singleType.estimatedSpeedup).toBeCloseTo(42, 0)
// Multi-type: 42/N where N = 2-5
expect(multiType.estimatedSpeedup).toBeGreaterThan(1)
expect(multiType.estimatedSpeedup).toBeLessThan(42)
// All-types: 42/42 = 1x
expect(allTypes.estimatedSpeedup).toBe(1.0)
})
it('should sort types by confidence in inference results', () => {
const plan = planner.planQuery('Find engineers and documents')
expect(plan.inferredTypes.length).toBeGreaterThan(0)
// Verify sorted by confidence (descending)
for (let i = 1; i < plan.inferredTypes.length; i++) {
expect(plan.inferredTypes[i - 1].confidence).toBeGreaterThanOrEqual(
plan.inferredTypes[i].confidence
)
}
})
})
// ========== Performance Tests (1 test) ==========
describe('Performance', () => {
it('should plan queries in < 5ms', () => {
const start = performance.now()
planner.planQuery('Find software engineers in San Francisco')
const elapsed = performance.now() - start
expect(elapsed).toBeLessThan(5) // Target: < 1ms, allow 5ms for CI
})
})
// ========== Statistics Tests (2 tests) ==========
describe('Query Statistics', () => {
it('should track query statistics', () => {
planner.planQuery('Find engineers') // single-type
planner.planQuery('engineers at companies') // multi-type
planner.planQuery('show everything') // all-types
const stats = planner.getStats()
expect(stats.totalQueries).toBe(3)
expect(stats.singleTypeQueries).toBeGreaterThan(0)
expect(stats.multiTypeQueries).toBeGreaterThan(0)
expect(stats.allTypesQueries).toBeGreaterThan(0)
expect(stats.avgConfidence).toBeGreaterThan(0)
})
it('should generate statistics report', () => {
planner.planQuery('Find engineers')
planner.planQuery('Find documents')
planner.planQuery('Show everything')
const report = planner.getStatsReport()
expect(report).toContain('Query Statistics')
expect(report).toContain('Single-type')
expect(report).toContain('Multi-type')
expect(report).toContain('All-types')
expect(report).toContain('Avg confidence')
expect(report).toContain('Avg speedup')
})
})
// ========== Batch Analysis Tests (1 test) ==========
describe('Batch Query Analysis', () => {
it('should analyze query distribution and provide recommendations', () => {
const queries = [
'Find engineers',
'Find developers',
'Show documents',
'List reports',
'Find companies',
'engineers at Tesla',
'documents about AI',
'show me everything',
'all the things',
'stuff'
]
const analysis = planner.analyzeQueries(queries)
expect(analysis).toHaveProperty('distribution')
expect(analysis).toHaveProperty('avgSpeedup')
expect(analysis).toHaveProperty('recommendations')
expect(analysis.distribution['single-type']).toBeGreaterThanOrEqual(0)
expect(analysis.distribution['multi-type']).toBeGreaterThanOrEqual(0)
expect(analysis.distribution['all-types']).toBeGreaterThanOrEqual(0)
const total =
analysis.distribution['single-type'] +
analysis.distribution['multi-type'] +
analysis.distribution['all-types']
expect(total).toBe(queries.length)
expect(analysis.avgSpeedup).toBeGreaterThan(0)
expect(Array.isArray(analysis.recommendations)).toBe(true)
})
})
// ========== Configuration Tests (2 tests) ==========
describe('Configuration', () => {
it('should respect custom confidence thresholds', () => {
const strictPlanner = new TypeAwareQueryPlanner(undefined, {
singleTypeThreshold: 0.95,
multiTypeThreshold: 0.85
})
// Query with moderate confidence should fall back to all-types
const plan = strictPlanner.planQuery('maybe find some engineers')
// With strict thresholds, this should use all-types or multi-type
expect(plan.routing).not.toBe('single-type')
})
it('should respect maxMultiTypes limit', () => {
const limitedPlanner = new TypeAwareQueryPlanner(undefined, {
maxMultiTypes: 2
})
const plan = limitedPlanner.planQuery(
'engineers at companies in cities working on projects with tools'
)
if (plan.routing === 'multi-type') {
expect(plan.targetTypes.length).toBeLessThanOrEqual(2)
}
})
})
// ========== Convenience Functions (1 test) ==========
describe('Global Convenience Functions', () => {
it('should provide planQuery() and getQueryPlanner() functions', () => {
const plan = planQuery('Find engineers')
expect(plan).toHaveProperty('routing')
expect(plan).toHaveProperty('targetTypes')
const globalPlanner = getQueryPlanner()
expect(globalPlanner).toBeInstanceOf(TypeAwareQueryPlanner)
})
})
})