promptforge
Version:
Adaptive Prompt Intelligence & Orchestration SDK - Manage, optimize, and serve prompts for LLMs with versioning, feedback loops, and multi-provider support
139 lines • 4.06 kB
JavaScript
import { v4 as uuidv4 } from 'uuid';
import { CacheEntrySchema } from '../types.js';
import crypto from 'crypto';
export class SemanticCache {
cache;
config;
constructor(config = {}) {
this.cache = new Map();
this.config = {
enabled: true,
ttlSeconds: 3600,
similarityThreshold: 0.95,
...config,
};
}
/**
* Get cached result for similar input
*/
async get(promptId, input) {
if (!this.config.enabled) {
return null;
}
const inputHash = this.hashInput(input);
// Find exact match first
for (const [, entry] of this.cache) {
if (entry.promptId === promptId && entry.inputHash === inputHash) {
// Check if expired
if (entry.expiresAt && new Date() > entry.expiresAt) {
this.cache.delete(entry.id);
continue;
}
// Increment hit count
entry.hitCount += 1;
return entry;
}
}
// TODO: Semantic similarity search using embeddings
// For now, return null if no exact match
return null;
}
/**
* Set cache entry
*/
async set(promptId, input, output, metadata) {
if (!this.config.enabled) {
return;
}
const inputHash = this.hashInput(input);
const expiresAt = new Date();
expiresAt.setSeconds(expiresAt.getSeconds() + this.config.ttlSeconds);
const entry = {
id: uuidv4(),
promptId,
inputHash,
inputEmbedding: [], // TODO: Generate embedding
output,
provider: metadata.provider,
model: metadata.model,
hitCount: 0,
createdAt: new Date(),
expiresAt,
metadata: { input },
};
const validatedEntry = CacheEntrySchema.parse(entry);
this.cache.set(entry.id, validatedEntry);
// Check max entries limit
if (this.config.maxEntries && this.cache.size > this.config.maxEntries) {
this.evictOldest();
}
}
/**
* Invalidate cache for a prompt
*/
async invalidate(promptId) {
let count = 0;
for (const [id, entry] of this.cache) {
if (entry.promptId === promptId) {
this.cache.delete(id);
count++;
}
}
return count;
}
/**
* Clear all cache
*/
async clear() {
this.cache.clear();
}
/**
* Get cache statistics
*/
getStats() {
const entries = Array.from(this.cache.values());
const totalHits = entries.reduce((sum, e) => sum + e.hitCount, 0);
return {
size: this.cache.size,
totalHits,
hitRate: this.cache.size > 0 ? totalHits / this.cache.size : 0,
};
}
/**
* Hash input for exact matching
*/
hashInput(input) {
const sorted = Object.keys(input)
.sort()
.map(key => `${key}:${input[key]}`)
.join('|');
return crypto.createHash('sha256').update(sorted).digest('hex');
}
/**
* Evict oldest entries
*/
evictOldest() {
const entries = Array.from(this.cache.entries());
entries.sort((a, b) => a[1].createdAt.getTime() - b[1].createdAt.getTime());
// Remove oldest 10%
const toRemove = Math.floor(entries.length * 0.1);
for (let i = 0; i < toRemove; i++) {
this.cache.delete(entries[i][0]);
}
}
/**
* Cleanup expired entries
*/
async cleanup() {
let count = 0;
const now = new Date();
for (const [id, entry] of this.cache) {
if (entry.expiresAt && now > entry.expiresAt) {
this.cache.delete(id);
count++;
}
}
return count;
}
}
//# sourceMappingURL=cache.js.map