/** * Hybrid Search Module * * Combines FTS5 keyword search with vector semantic search * for improved recall (15-20% better than either alone). * * Features: * - SQLite FTS5 full-text search with trigram tokenizer (CJK support) * - Score fusion (α*keyword + β*semantic) * - 3-layer search workflow for token efficiency * - Token economics tracking * * CJK Language Support: * Uses trigram tokenizer which works for Japanese, Chinese, Korean * by matching substrings instead of requiring word boundaries. * * @module @aitytech/agentkits-memory/search */ import type { Database as BetterDatabase } from 'better-sqlite3'; import type { MemoryEntry, SearchResult, EmbeddingGenerator } from '../types.js'; /** * Hybrid search configuration */ export interface HybridSearchConfig { /** Weight for keyword/FTS5 score (0-1, default: 0.3) */ keywordWeight: number; /** Weight for semantic/vector score (0-1, default: 0.7) */ semanticWeight: number; /** Minimum combined score threshold (0-1, default: 0.1) */ minScore: number; /** Enable BM25 scoring for FTS5 (default: true) */ useBM25: boolean; /** Maximum results per search layer (default: 100) */ maxResultsPerLayer: number; /** * FTS5 tokenizer to use (default: 'trigram') * - 'trigram': Best for CJK languages (Japanese, Chinese, Korean) * - 'unicode61': Standard tokenizer, English/Latin only * - 'porter': Stemming for English */ tokenizer: 'trigram' | 'unicode61' | 'porter'; /** Fall back to LIKE search if FTS5 unavailable (default: true) */ fallbackToLike: boolean; } /** * Compact search result (Layer 1) * Minimal data for initial filtering - saves tokens */ export interface CompactSearchResult { /** Entry ID */ id: string; /** Entry key */ key: string; /** Namespace */ namespace: string; /** Combined relevance score (0-1) */ score: number; /** Keyword match score */ keywordScore: number; /** Semantic similarity score */ semanticScore: number; /** Preview snippet (first 100 chars) */ snippet: string; /** Estimated token count */ estimatedTokens: number; } /** * Timeline result (Layer 2) * Context around search results */ export interface TimelineResult { /** The target entry */ entry: CompactSearchResult; /** Related entries before (chronologically) */ before: CompactSearchResult[]; /** Related entries after (chronologically) */ after: CompactSearchResult[]; /** Total context window tokens */ totalTokens: number; } /** * Token economics for search operations */ export interface TokenEconomics { /** Tokens saved by using compact results */ tokensSaved: number; /** Tokens that would be used with full results */ fullResultTokens: number; /** Actual tokens used */ actualTokens: number; /** Savings percentage */ savingsPercent: number; /** Layer breakdown */ layers: { compact: number; timeline: number; full: number; }; } /** * Full search result with economics */ export interface HybridSearchResult { /** Search results */ results: SearchResult[]; /** Compact results (layer 1) */ compact: CompactSearchResult[]; /** Token economics */ economics: TokenEconomics; /** Search timing */ timing: { keywordMs: number; semanticMs: number; fusionMs: number; totalMs: number; }; } /** * Hybrid Search Engine * * Provides enterprise-grade search combining keyword and semantic search * with token-efficient 3-layer retrieval workflow. * * Supports CJK languages (Japanese, Chinese, Korean) via trigram tokenizer. */ export declare class HybridSearchEngine { private db; private config; private embeddingGenerator?; private ftsInitialized; private ftsAvailable; /** The actual tokenizer being used (may differ from config if tokenizer not available) */ private activeTokenizer; constructor(db: BetterDatabase, config?: Partial, embeddingGenerator?: EmbeddingGenerator); /** * Check if FTS5 is available in this SQLite build */ private checkFts5Available; /** * Check if a specific tokenizer is available */ private checkTokenizerAvailable; /** * Get the best available tokenizer for FTS5 * Tries trigram first (best for CJK), then unicode61, then porter * Also sets the activeTokenizer field */ private getBestTokenizer; /** * Initialize FTS5 virtual table * Note: For best CJK support, use better-sqlite3 which includes trigram tokenizer. */ initialize(): Promise; /** * Check if FTS5 is available and initialized */ isFtsAvailable(): boolean; /** * Get the active tokenizer being used * Returns null if FTS5 is not available */ getActiveTokenizer(): 'trigram' | 'unicode61' | 'porter' | null; /** * Check if CJK search is fully supported (requires trigram tokenizer) * If not, CJK queries will fall back to LIKE search */ isCjkOptimized(): boolean; /** * Rebuild FTS index from existing memory entries * Uses the FTS5 'rebuild' command for content-synced tables */ rebuildFtsIndex(): Promise; /** * Layer 1: Compact Search * * Returns minimal data for initial filtering. * ~10x token savings vs full results. */ searchCompact(query: string, options?: { limit?: number; namespace?: string; includeKeyword?: boolean; includeSemantic?: boolean; }): Promise; /** * Layer 2: Timeline Search * * Returns context around matched entries. * Useful for understanding temporal relationships. */ searchTimeline(entryIds: string[], contextWindow?: number): Promise; /** * Layer 3: Full Search * * Returns complete entry data for selected IDs. * Only fetch what you need after filtering. */ getFull(ids: string[]): Promise; /** * Full hybrid search with token economics * * Combines all three layers with detailed metrics. */ search(query: string, options?: { limit?: number; namespace?: string; fetchFull?: boolean; }): Promise; /** * Check if text contains CJK characters * CJK requires special handling (LIKE or trigram tokenizer) */ private containsCJK; /** * Keyword search using FTS5 (with LIKE fallback) * * For CJK languages, automatically falls back to LIKE search * unless trigram tokenizer is available. */ private keywordSearch; /** * LIKE-based search fallback (works without FTS5) * * Less efficient but supports all languages. */ private likeSearch; /** * Semantic search returning compact results */ private semanticSearchCompact; /** * Fuse keyword and semantic scores */ private fuseScores; /** * Calculate cosine similarity between two vectors */ private cosineSimilarity; /** * Sanitize query for FTS5 * Preserves CJK characters (Japanese, Chinese, Korean) and basic alphanumerics */ private sanitizeFtsQuery; /** * Convert database row to MemoryEntry */ private rowToEntry; /** * Get configuration */ getConfig(): HybridSearchConfig; /** * Update configuration */ updateConfig(config: Partial): void; } /** * Create a hybrid search engine */ export declare function createHybridSearchEngine(db: BetterDatabase, config?: Partial, embeddingGenerator?: EmbeddingGenerator): HybridSearchEngine; export default HybridSearchEngine; //# sourceMappingURL=hybrid-search.d.ts.map