Skip to main content
Glama
advancedCache.js6.26 kB
// src/utils/advancedCache.js // Advanced caching system with semantic similarity and cost optimization const NodeCache = require('node-cache'); const crypto = require('crypto'); const dbClient = require('./dbClient'); const config = require('../../config'); class AdvancedCache { constructor() { // Multi-tier caching system this.resultCache = new NodeCache({ stdTTL: config.caching?.results?.ttlSeconds || 7200, maxKeys: config.caching?.results?.maxEntries || 1000, checkperiod: 600 // Check for expired keys every 10 minutes }); this.modelCache = new NodeCache({ stdTTL: config.caching?.models?.ttlSeconds || 3600, maxKeys: config.caching?.models?.maxEntries || 500, checkperiod: 300 // Check for expired keys every 5 minutes }); this.similarityThreshold = config.caching?.results?.similarityThreshold || 0.85; this.enabled = config.caching?.results?.enabled !== false; } // Generate semantic cache key with query normalization generateSemanticKey(query, params = {}) { const normalizedQuery = query.toLowerCase().trim(); const sortedParams = Object.keys(params).sort().reduce((obj, key) => { obj[key] = params[key]; return obj; }, {}); const content = `${normalizedQuery}:${JSON.stringify(sortedParams)}`; return crypto.createHash('sha256').update(content).digest('hex').substring(0, 16); } // Check for semantically similar cached results async findSimilarResult(query, params = {}) { if (!this.enabled) return null; try { // First check exact match const exactKey = this.generateSemanticKey(query, params); const exactMatch = this.resultCache.get(exactKey); if (exactMatch) { console.error(`[${new Date().toISOString()}] AdvancedCache: Exact cache hit for query "${query.substring(0, 50)}..."`); return { ...exactMatch, cacheType: 'exact' }; } // Check semantic similarity using database vector search const similar = await dbClient.findReportsBySimilarity(query, 3, this.similarityThreshold); if (similar && similar.length > 0) { const bestMatch = similar[0]; const sim = typeof bestMatch.similarityScore === 'number' ? bestMatch.similarityScore : (bestMatch.similarity || 0); const reportId = String(bestMatch.id || bestMatch._id || ''); const content = bestMatch.final_report || bestMatch.finalReport || ''; const originalQuery = bestMatch.original_query || bestMatch.originalQuery || ''; console.error(`[${new Date().toISOString()}] AdvancedCache: Semantic cache hit (similarity: ${sim?.toFixed ? sim.toFixed(3) : sim}) for query "${query.substring(0, 50)}..."`); // Cache the semantic match for future exact retrieval this.resultCache.set(exactKey, { result: content, reportId: reportId, similarity: sim, originalQuery: originalQuery, timestamp: new Date().toISOString() }); return { result: content, reportId: reportId, cacheType: 'semantic', similarity: sim }; } console.error(`[${new Date().toISOString()}] AdvancedCache: Cache miss for query "${query.substring(0, 50)}..."`); return null; } catch (error) { console.error(`[${new Date().toISOString()}] AdvancedCache: Error checking cache:`, error); return null; } } // Store result with semantic enrichment async storeResult(query, params, result, reportId = null) { if (!this.enabled) return; try { const key = this.generateSemanticKey(query, params); const cacheEntry = { result, reportId, query, params, timestamp: new Date().toISOString(), cost: this.calculateCost(result) // Estimate cost for tracking }; this.resultCache.set(key, cacheEntry); console.error(`[${new Date().toISOString()}] AdvancedCache: Stored result for query "${query.substring(0, 50)}..." (key: ${key})`); } catch (error) { console.error(`[${new Date().toISOString()}] AdvancedCache: Error storing result:`, error); } } // Model response caching for repeated API calls cacheModelResponse(model, messages, response) { if (!this.enabled) return; const key = this.generateModelKey(model, messages); this.modelCache.set(key, { response, model, timestamp: new Date().toISOString(), usage: response.usage }); } getCachedModelResponse(model, messages) { if (!this.enabled) return null; const key = this.generateModelKey(model, messages); return this.modelCache.get(key); } generateModelKey(model, messages) { const content = `${model}:${JSON.stringify(messages)}`; return crypto.createHash('sha256').update(content).digest('hex').substring(0, 16); } // Cost estimation for cache value assessment calculateCost(result) { const estimatedTokens = Math.ceil((result?.length || 0) / 4); // Rough token estimate return estimatedTokens * 0.000001; // Rough cost estimate } // Get cache statistics for monitoring getStats() { return { results: { keys: this.resultCache.keys().length, hits: this.resultCache.getStats().hits, misses: this.resultCache.getStats().misses, size: this.resultCache.getStats().vsize }, models: { keys: this.modelCache.keys().length, hits: this.modelCache.getStats().hits, misses: this.modelCache.getStats().misses, size: this.modelCache.getStats().vsize }, config: { enabled: this.enabled, similarityThreshold: this.similarityThreshold, resultTTL: config.caching?.results?.ttlSeconds || 7200, modelTTL: config.caching?.models?.ttlSeconds || 3600 } }; } // Clear cache (for maintenance) clear(type = 'all') { if (type === 'all' || type === 'results') { this.resultCache.flushAll(); } if (type === 'all' || type === 'models') { this.modelCache.flushAll(); } console.error(`[${new Date().toISOString()}] AdvancedCache: Cleared ${type} cache`); } } module.exports = new AdvancedCache();

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/wheattoast11/openrouter-deep-research-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server