Cache
Évitez les appels LLM et embedding redondants grâce à un cache intelligent. Réduisez la latence, économisez des coûts et améliorez le débit avec des caches en mémoire ou Redis.
Pourquoi le Cache ?
Les appels API LLM sont coûteux et lents. Lorsque le même prompt est envoyé plusieurs fois (ex : questions utilisateur répétées, traitement par lots ou itérations de développement), le cache évite les appels API redondants en retournant instantanément les résultats précédemment calculés.
Latence cache hit (vs 500-3000ms appel API)
Coût par réponse en cache
Résultats déterministes
# Architecture
Le système de cache d'Orka AI est composé de trois couches qui fonctionnent ensemble :
1. Cache Store (Backend de Stockage)
Où les données en cache sont stockées. Choisissez entre MemoryCache (en processus, rapide, sans dépendances) ou RedisCache (distribué, persistant, partagé entre instances).
2. CachedLLM (Cache de Réponses LLM)
Enveloppe n'importe quel LLMAdapter et met en cache les réponses generate(). Même prompt + options = réponse en cache instantanée.
3. CachedEmbeddings (Cache d'Embeddings)
Met en cache les vecteurs d'embedding par texte d'entrée. Évite de recalculer les embeddings pour les chunks de texte déjà vus.
# MemoryCache (En Mémoire)
Le store de cache le plus simple. Les données sont stockées en processus via une Map. Aucune dépendance externe requise. Idéal pour le développement, les serveurs mono-instance et les processus de courte durée.
import { MemoryCache } from 'orkajs/cache/memory'; const cache = new MemoryCache({ maxSize: 1000, // Maximum number of entries (default: 1000) ttlMs: 1000 * 60 * 30, // Time-to-live: 30 minutes (optional) namespace: 'my-app' // Key prefix for isolation (optional)}); // Basic operationsawait cache.set('key', { data: 'value' });const value = await cache.get('key'); // { data: 'value' }const exists = await cache.has('key'); // trueawait cache.delete('key');await cache.clear(); // Get cache statisticsconst stats = cache.getStats();console.log(stats);// { hits: 42, misses: 8, size: 150, hitRate: 0.84 }🧠 Fonctionnalités MemoryCache
- ✅ Zéro dépendances — fonctionne immédiatement
- ✅ Support TTL — les entrées expirent automatiquement
- ✅ Taille max avec éviction type LRU (plus ancien d'abord)
- ✅ Isolation par namespace — plusieurs caches dans une instance
- ✅ Statistiques hit/miss pour le monitoring
# RedisCache (Distribué)
Pour les environnements de production avec plusieurs instances de serveur, Redis fournit un cache partagé et persistant. Les données survivent aux redémarrages du serveur et sont accessibles depuis n'importe quelle instance.
📦 Installation Requise
RedisCache nécessite le package redis :
npm install redisimport { RedisCache } from 'orkajs/cache/redis'; const cache = new RedisCache({ url: 'redis://localhost:6379', // Redis connection URL keyPrefix: 'orka:', // Key prefix (default: 'orka:') ttlMs: 1000 * 60 * 60, // TTL: 1 hour (optional)}); // Connect to Redis (auto-connects on first operation)await cache.connect(); // Same API as MemoryCacheawait cache.set('key', { data: 'value' });const value = await cache.get('key'); // Disconnect when doneawait cache.disconnect();MemoryCache
- ✅ Pas de dépendances
- ✅ Le plus rapide (en processus)
- ❌ Perdu au redémarrage
- ❌ Non partagé entre instances
RedisCache
- ✅ Persistant entre redémarrages
- ✅ Partagé entre instances
- ❌ Nécessite un serveur Redis
- ❌ Latence réseau (~1ms)
# CachedLLM — Cache de Réponses LLM
CachedLLM enveloppe n'importe quel LLMAdapter et met en cache les réponses de manière transparente. Il implémente l'interface LLMAdapter, vous pouvez donc l'utiliser comme remplacement direct partout où vous utilisez un LLM.
import { OpenAIAdapter } from 'orkajs/adapters/openai';import { MemoryCache } from 'orkajs/cache/memory';import { CachedLLM } from 'orkajs/cache/llm'; // 1. Create your LLM adapterconst llm = new OpenAIAdapter({ apiKey: process.env.OPENAI_API_KEY!, model: 'gpt-4o-mini'}); // 2. Create a cache storeconst cache = new MemoryCache({ maxSize: 500, ttlMs: 1000 * 60 * 30 }); // 3. Wrap with CachedLLMconst cachedLLM = new CachedLLM(llm, cache, { ttlMs: 1000 * 60 * 60 // Override TTL: cache for 1 hour}); // First call — hits the API (~800ms)const result1 = await cachedLLM.generate('What is TypeScript?');console.log(result1.content); // "TypeScript is a typed superset..." // Second call — instant from cache (~0ms)const result2 = await cachedLLM.generate('What is TypeScript?');console.log(result2.content); // Same response, from cache // Use in Orka config — transparent replacementimport { createOrka } from 'orkajs'; const orka = createOrka({ llm: cachedLLM, // ← Drop-in replacement vectorDB: /* ... */}); // All orka.ask(), orka.generate(), etc. now use cachingconst answer = await orka.ask({ question: 'What is TypeScript?', knowledge: 'docs'});🔑 Génération de Clé de Cache
La clé de cache est générée à partir du prompt ET des options (temperature, maxTokens, systemPrompt, etc.). Cela signifie :
- Même prompt + mêmes options = cache hit ✅
- Même prompt + température différente = cache miss ❌ (clé différente)
# CachedEmbeddings — Cache d'Embeddings
Embedder le même texte plusieurs fois est du gaspillage. CachedEmbeddings met en cache les vecteurs d'embedding par texte d'entrée et n'envoie à l'API que les textes non mis en cache.
import { OpenAIAdapter } from 'orkajs/adapters/openai';import { MemoryCache } from 'orkajs/cache/memory';import { CachedEmbeddings } from 'orkajs/cache/embeddings'; const llm = new OpenAIAdapter({ apiKey: process.env.OPENAI_API_KEY! });const cache = new MemoryCache({ maxSize: 10000 }); const cachedEmbed = new CachedEmbeddings(llm, cache, { ttlMs: 1000 * 60 * 60 * 24 // Cache embeddings for 24 hours}); // First call — computes all 3 embeddings via APIconst embeddings1 = await cachedEmbed.embed([ 'Hello world', 'TypeScript is great', 'Orka AI framework']); // Second call — all 3 from cache (0 API calls)const embeddings2 = await cachedEmbed.embed([ 'Hello world', 'TypeScript is great', 'Orka AI framework']); // Mixed call — only "New text" hits the API, others from cacheconst embeddings3 = await cachedEmbed.embed([ 'Hello world', // ← from cache 'New text here', // ← API call 'Orka AI framework' // ← from cache]);⚡ Batching Intelligent
CachedEmbeddings vérifie le cache pour chaque texte individuellement, puis regroupe uniquement les textes non mis en cache en un seul appel API. Cela signifie que si vous embeddez 100 textes et 80 sont en cache, seuls 20 sont envoyés à l'API en un seul lot.
# Configuration Production avec Redis
En production, utilisez RedisCache pour partager le cache entre plusieurs instances de serveur et persister les données entre les redémarrages.
import { createOrka, OpenAIAdapter } from 'orkajs/core';import { RedisCache } from 'orkajs/cache/redis';import { CachedLLM } from 'orkajs/cache/llm';import { CachedEmbeddings } from 'orkajs/cache/embeddings'; // Shared Redis cacheconst redisCache = new RedisCache({ url: process.env.REDIS_URL!, // e.g. 'redis://redis:6379' keyPrefix: 'orka:prod:', ttlMs: 1000 * 60 * 60 * 4 // 4 hours}); const llm = new OpenAIAdapter({ apiKey: process.env.OPENAI_API_KEY!, model: 'gpt-4o-mini'}); // Cache both LLM responses and embeddingsconst cachedLLM = new CachedLLM(llm, redisCache);const cachedEmbed = new CachedEmbeddings(llm, redisCache); const orka = createOrka({ llm: cachedLLM, vectorDB: /* ... */}); // All operations now use Redis-backed cachingconst result = await orka.ask({ question: 'How do I deploy my app?', knowledge: 'documentation'}); // Monitor cache performanceconst stats = redisCache.getStats();console.log(`Cache hit rate: ${(stats.hitRate * 100).toFixed(1)}%`); // Cleanup on shutdownprocess.on('SIGTERM', async () => { await redisCache.disconnect();});# Cache Store Personnalisé
Implémentez l'interface CacheStore pour créer votre propre backend de cache (ex : DynamoDB, Memcached, SQLite).
import type { CacheStore } from 'orkajs/cache'; class DynamoDBCache implements CacheStore { readonly name = 'dynamodb-cache'; async get<T>(key: string): Promise<T | undefined> { // Your DynamoDB get logic } async set<T>(key: string, value: T, ttlMs?: number): Promise<void> { // Your DynamoDB put logic } async delete(key: string): Promise<boolean> { // Your DynamoDB delete logic } async clear(): Promise<void> { // Your DynamoDB scan + delete logic } async has(key: string): Promise<boolean> { // Your DynamoDB exists check }} // Use with CachedLLMconst cache = new DynamoDBCache();const cachedLLM = new CachedLLM(llm, cache);Bonnes Pratiques
1. Définissez des TTL Appropriés
TTL court (5-30 min) pour le contenu dynamique. TTL long (heures/jours) pour les bases de connaissances stables. Pas de TTL pour les données immuables comme les embeddings.
2. Surveillez les Taux de Hit
Utilisez getStats() pour surveiller l'efficacité du cache. Un taux de hit inférieur à 50% peut indiquer que le cache est trop petit ou le TTL trop court.
3. Ne Cachez Pas les Appels Non-Déterministes
Si vous utilisez une température élevée (>0.8) pour la génération créative, le cache peut retourner des sorties créatives obsolètes. Envisagez de désactiver le cache pour les tâches créatives.
4. Utilisez les Namespaces
Utilisez des namespaces ou préfixes de clé différents pour les différents environnements (dev, staging, prod) pour éviter la pollution du cache.
Imports Tree-shakeable
// ✅ Import only what you needimport { MemoryCache } from 'orkajs/cache/memory';import { CachedLLM } from 'orkajs/cache/llm'; // ✅ Or import from indeximport { MemoryCache, RedisCache, CachedLLM, CachedEmbeddings } from 'orkajs/cache';