groundswell 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (120) hide show
  1. package/.claude/settings.local.json +9 -0
  2. package/.claude/system_prompts/task-breakdown.md +100 -0
  3. package/PRPs/001-hierarchical-workflow-engine.md +2438 -0
  4. package/PRPs/PRDs/001-hierarchical-workflow-engine.md +543 -0
  5. package/PRPs/PRDs/002-agent-prompt.md +390 -0
  6. package/PRPs/PRDs/003-agent-prompt.md +943 -0
  7. package/PRPs/PRDs/004-agent-prompt.md +1136 -0
  8. package/PRPs/PRDs/tasks-001.json +492 -0
  9. package/PRPs/README.md +83 -0
  10. package/PRPs/templates/prp_base.md +222 -0
  11. package/README.md +218 -0
  12. package/docs/agent.md +422 -0
  13. package/docs/prompt.md +419 -0
  14. package/docs/workflow.md +600 -0
  15. package/examples/README.md +244 -0
  16. package/examples/examples/01-basic-workflow.ts +100 -0
  17. package/examples/examples/02-decorator-options.ts +217 -0
  18. package/examples/examples/03-parent-child.ts +241 -0
  19. package/examples/examples/04-observers-debugger.ts +340 -0
  20. package/examples/examples/05-error-handling.ts +387 -0
  21. package/examples/examples/06-concurrent-tasks.ts +352 -0
  22. package/examples/examples/07-agent-loops.ts +432 -0
  23. package/examples/examples/08-sdk-features.ts +667 -0
  24. package/examples/examples/09-reflection.ts +573 -0
  25. package/examples/examples/10-introspection.ts +550 -0
  26. package/examples/index.ts +143 -0
  27. package/examples/utils/helpers.ts +57 -0
  28. package/llms_full.txt +5890 -0
  29. package/package.json +63 -0
  30. package/plan/P1P2/PRP.md +527 -0
  31. package/plan/P1P2/research/LRU_CACHE_BEST_PRACTICES.md +1929 -0
  32. package/plan/P1P2/research/LRU_CACHE_CODE_PATTERNS.md +857 -0
  33. package/plan/P1P2/research/LRU_CACHE_INTEGRATION_GUIDE.md +738 -0
  34. package/plan/P1P2/research/LRU_CACHE_RESEARCH_INDEX.md +424 -0
  35. package/plan/P1P2/research/REFLECTION_INDEX.md +291 -0
  36. package/plan/P1P2/research/REFLECTION_RESEARCH_REPORT.md +1342 -0
  37. package/plan/P1P2/research/RESEARCH_SUMMARY.md +342 -0
  38. package/plan/P1P2/research/anthropic-sdk.md +174 -0
  39. package/plan/P1P2/research/async-local-storage.md +200 -0
  40. package/plan/P1P2/research/reflection-code-patterns.md +1205 -0
  41. package/plan/P1P2/research/reflection-decision-matrix.md +421 -0
  42. package/plan/P1P2/research/reflection-implementation-guide.md +1341 -0
  43. package/plan/P1P2/research/reflection-integration-guide.md +834 -0
  44. package/plan/P1P2/research/reflection-patterns.md +1468 -0
  45. package/plan/P1P2/research/reflection-quick-reference.md +558 -0
  46. package/plan/P1P2/research/zod-schema.md +152 -0
  47. package/plan/P3P4/PRP.md +1388 -0
  48. package/plan/P3P4/research/caching-lru.md +116 -0
  49. package/plan/P3P4/research/introspection-tools.md +177 -0
  50. package/plan/P3P4/research/reflection-patterns.md +117 -0
  51. package/plan/P4P5/PRP.md +1136 -0
  52. package/plan/P4P5/research/RESEARCH_SUMMARY.md +151 -0
  53. package/plan/architecture/external_deps.md +358 -0
  54. package/plan/architecture/system_context.md +242 -0
  55. package/plan/backlog.json +867 -0
  56. package/plan/research/INTROSPECTION_RESEARCH_SUMMARY.md +378 -0
  57. package/plan/research/README-INTROSPECTION.md +352 -0
  58. package/plan/research/agent-introspection-patterns.md +1085 -0
  59. package/plan/research/introspection-security-guide.md +928 -0
  60. package/plan/research/introspection-tool-examples.md +875 -0
  61. package/scripts/generate-llms-full.ts +206 -0
  62. package/src/__tests__/integration/agent-workflow.test.ts +256 -0
  63. package/src/__tests__/integration/tree-mirroring.test.ts +114 -0
  64. package/src/__tests__/unit/agent.test.ts +169 -0
  65. package/src/__tests__/unit/cache-key.test.ts +182 -0
  66. package/src/__tests__/unit/cache.test.ts +172 -0
  67. package/src/__tests__/unit/context.test.ts +138 -0
  68. package/src/__tests__/unit/decorators.test.ts +100 -0
  69. package/src/__tests__/unit/introspection-tools.test.ts +277 -0
  70. package/src/__tests__/unit/prompt.test.ts +135 -0
  71. package/src/__tests__/unit/reflection.test.ts +210 -0
  72. package/src/__tests__/unit/tree-debugger.test.ts +85 -0
  73. package/src/__tests__/unit/workflow.test.ts +81 -0
  74. package/src/cache/cache-key.ts +244 -0
  75. package/src/cache/cache.ts +236 -0
  76. package/src/cache/index.ts +8 -0
  77. package/src/core/agent.ts +573 -0
  78. package/src/core/context.ts +119 -0
  79. package/src/core/event-tree.ts +260 -0
  80. package/src/core/factory.ts +123 -0
  81. package/src/core/index.ts +17 -0
  82. package/src/core/logger.ts +87 -0
  83. package/src/core/mcp-handler.ts +184 -0
  84. package/src/core/prompt.ts +150 -0
  85. package/src/core/workflow-context.ts +349 -0
  86. package/src/core/workflow.ts +302 -0
  87. package/src/debugger/index.ts +1 -0
  88. package/src/debugger/tree-debugger.ts +210 -0
  89. package/src/decorators/index.ts +3 -0
  90. package/src/decorators/observed-state.ts +95 -0
  91. package/src/decorators/step.ts +139 -0
  92. package/src/decorators/task.ts +96 -0
  93. package/src/examples/index.ts +2 -0
  94. package/src/examples/tdd-orchestrator.ts +65 -0
  95. package/src/examples/test-cycle-workflow.ts +64 -0
  96. package/src/index.ts +140 -0
  97. package/src/reflection/index.ts +5 -0
  98. package/src/reflection/reflection.ts +407 -0
  99. package/src/tools/index.ts +36 -0
  100. package/src/tools/introspection.ts +464 -0
  101. package/src/types/agent.ts +90 -0
  102. package/src/types/decorators.ts +25 -0
  103. package/src/types/error-strategy.ts +13 -0
  104. package/src/types/error.ts +20 -0
  105. package/src/types/events.ts +74 -0
  106. package/src/types/index.ts +55 -0
  107. package/src/types/logging.ts +24 -0
  108. package/src/types/observer.ts +18 -0
  109. package/src/types/prompt.ts +40 -0
  110. package/src/types/reflection.ts +117 -0
  111. package/src/types/sdk-primitives.ts +128 -0
  112. package/src/types/snapshot.ts +14 -0
  113. package/src/types/workflow-context.ts +163 -0
  114. package/src/types/workflow.ts +37 -0
  115. package/src/utils/id.ts +11 -0
  116. package/src/utils/index.ts +3 -0
  117. package/src/utils/observable.ts +77 -0
  118. package/tasks.json +0 -0
  119. package/tsconfig.json +22 -0
  120. package/vitest.config.ts +16 -0
@@ -0,0 +1,857 @@
1
+ # LRU Cache Code Patterns for LLM Caching
2
+
3
+ **Quick Reference Guide with Copy-Paste Ready Examples**
4
+
5
+ ---
6
+
7
+ ## Table of Contents
8
+
9
+ 1. [Installation](#installation)
10
+ 2. [Quick Start Pattern](#quick-start-pattern)
11
+ 3. [Cache Key Generation Patterns](#cache-key-generation-patterns)
12
+ 4. [Configuration Patterns](#configuration-patterns)
13
+ 5. [Usage Patterns](#usage-patterns)
14
+ 6. [Testing Patterns](#testing-patterns)
15
+ 7. [Monitoring Patterns](#monitoring-patterns)
16
+
17
+ ---
18
+
19
+ ## Installation
20
+
21
+ ```bash
22
+ # Core dependencies
23
+ npm install lru-cache safe-stable-stringify zod
24
+
25
+ # Optional: For semantic caching
26
+ npm install @xenova/transformers # For embeddings
27
+
28
+ # TypeScript support
29
+ npm install --save-dev typescript @types/node
30
+ ```
31
+
32
+ **Package.json:**
33
+
34
+ ```json
35
+ {
36
+ "dependencies": {
37
+ "lru-cache": "^10.0.0",
38
+ "safe-stable-stringify": "^2.4.0",
39
+ "zod": "^3.22.0"
40
+ },
41
+ "devDependencies": {
42
+ "typescript": "^5.2.0",
43
+ "@types/node": "^20.0.0"
44
+ }
45
+ }
46
+ ```
47
+
48
+ ---
49
+
50
+ ## Quick Start Pattern
51
+
52
+ ### Minimal Working Example
53
+
54
+ ```typescript
55
+ import { LRUCache } from 'lru-cache';
56
+ import { createHash } from 'node:crypto';
57
+ import safeStringify from 'safe-stable-stringify';
58
+
59
+ // 1. Create cache
60
+ const cache = new LRUCache<string, string>({
61
+ max: 100,
62
+ ttl: 3600000 // 1 hour
63
+ });
64
+
65
+ // 2. Generate deterministic key
66
+ function cacheKey(input: any): string {
67
+ const normalized = safeStringify(input);
68
+ const hash = createHash('sha256');
69
+ hash.update(normalized);
70
+ return hash.digest('hex');
71
+ }
72
+
73
+ // 3. Use cache
74
+ async function getCachedResponse(prompt: string): Promise<string> {
75
+ const key = cacheKey({ prompt });
76
+
77
+ return cache.fetch(
78
+ key,
79
+ async () => {
80
+ // This only runs on cache miss
81
+ const response = await expensiveOperation(prompt);
82
+ return response;
83
+ }
84
+ );
85
+ }
86
+ ```
87
+
88
+ ---
89
+
90
+ ## Cache Key Generation Patterns
91
+
92
+ ### Pattern 1: Simple String Hashing
93
+
94
+ ```typescript
95
+ function simpleKeyHash(text: string): string {
96
+ return createHash('sha256').update(text).digest('hex');
97
+ }
98
+
99
+ // Usage
100
+ const key = simpleKeyHash('user prompt text');
101
+ ```
102
+
103
+ ### Pattern 2: Object Hashing (Most Common)
104
+
105
+ ```typescript
106
+ function objectKeyHash(obj: Record<string, any>): string {
107
+ const normalized = safeStringify(obj);
108
+ const hash = createHash('sha256');
109
+ hash.update(normalized);
110
+ return hash.digest('hex').substring(0, 16); // 16 chars for readability
111
+ }
112
+
113
+ // Usage
114
+ const key = objectKeyHash({
115
+ model: 'gpt-4',
116
+ temperature: 0.7,
117
+ prompt: 'What is AI?'
118
+ });
119
+ ```
120
+
121
+ ### Pattern 3: Composite Keys with Prefix
122
+
123
+ ```typescript
124
+ function compositeKey(
125
+ model: string,
126
+ version: string,
127
+ input: Record<string, any>
128
+ ): string {
129
+ const hash = objectKeyHash(input);
130
+ return `${model}:${version}:${hash}`;
131
+ }
132
+
133
+ // Usage
134
+ const key = compositeKey('gpt-4', 'v1', { prompt: '...' });
135
+ // Output: gpt-4:v1:a1b2c3d4e5f6g7h8
136
+ ```
137
+
138
+ ### Pattern 4: Semantic Cache Key (Embedding-based)
139
+
140
+ ```typescript
141
+ async function semanticCacheKey(
142
+ prompt: string,
143
+ embeddingModel: any
144
+ ): Promise<string> {
145
+ // Get embedding vector
146
+ const embedding = await embeddingModel.embed(prompt);
147
+
148
+ // Round to 2 decimal places for compression
149
+ const compressed = embedding
150
+ .slice(0, 10) // Take first 10 dimensions
151
+ .map((v: number) => Math.round(v * 100) / 100);
152
+
153
+ return safeStringify({
154
+ type: 'semantic',
155
+ embedding: compressed
156
+ });
157
+ }
158
+ ```
159
+
160
+ ### Pattern 5: Versioned Keys (Auto-invalidate on schema change)
161
+
162
+ ```typescript
163
+ class VersionedKeyGenerator {
164
+ private version: number = 1;
165
+
166
+ constructor(private modelName: string) {}
167
+
168
+ generate(input: any): string {
169
+ const normalized = safeStringify({
170
+ version: this.version,
171
+ model: this.modelName,
172
+ input
173
+ });
174
+
175
+ const hash = createHash('sha256');
176
+ hash.update(normalized);
177
+ return hash.digest('hex');
178
+ }
179
+
180
+ // Bump version to invalidate all old cache entries
181
+ invalidateAll(): void {
182
+ this.version++;
183
+ }
184
+ }
185
+
186
+ // Usage
187
+ const keyGen = new VersionedKeyGenerator('gpt-4');
188
+ const key1 = keyGen.generate({ prompt: 'hello' });
189
+
190
+ keyGen.invalidateAll(); // All old keys now invalid
191
+
192
+ const key2 = keyGen.generate({ prompt: 'hello' });
193
+ // key1 !== key2 (different version)
194
+ ```
195
+
196
+ ---
197
+
198
+ ## Configuration Patterns
199
+
200
+ ### Pattern 1: Development Cache (Small, Short-lived)
201
+
202
+ ```typescript
203
+ const devCache = new LRUCache<string, any>({
204
+ max: 100,
205
+ ttl: 600000, // 10 minutes
206
+ updateAgeOnGet: true
207
+ });
208
+ ```
209
+
210
+ ### Pattern 2: Production Cache (Large, Long-lived)
211
+
212
+ ```typescript
213
+ const prodCache = new LRUCache<string, any>({
214
+ max: 5000,
215
+ maxSize: 500 * 1024 * 1024, // 500 MB
216
+ ttl: 24 * 3600 * 1000, // 24 hours
217
+ sizeCalculation: (val) => {
218
+ const json = JSON.stringify(val);
219
+ return Buffer.byteLength(json, 'utf8') + 100;
220
+ },
221
+ updateAgeOnGet: true
222
+ });
223
+ ```
224
+
225
+ ### Pattern 3: Memory-Constrained Cache
226
+
227
+ ```typescript
228
+ const memoryConstrainedCache = new LRUCache<string, any>({
229
+ max: 500, // Limit by item count instead of size
230
+ ttl: 3600000, // 1 hour
231
+ updateAgeOnGet: false // Don't refresh on every access
232
+ });
233
+ ```
234
+
235
+ ### Pattern 4: High-Throughput Cache
236
+
237
+ ```typescript
238
+ const highThroughputCache = new LRUCache<string, any>({
239
+ max: 10000,
240
+ ttl: 1800000, // 30 minutes
241
+ updateAgeOnGet: true, // Keep hot items fresh
242
+
243
+ // Optional: Track evictions
244
+ dispose: (value, key, reason) => {
245
+ if (reason === 'evict') {
246
+ console.log(`Evicted key: ${key.substring(0, 8)}...`);
247
+ }
248
+ }
249
+ });
250
+ ```
251
+
252
+ ### Pattern 5: Persistent + In-Memory Hybrid
253
+
254
+ ```typescript
255
+ import { promises as fs } from 'node:fs';
256
+
257
+ class HybridCache {
258
+ private memory: LRUCache<string, any>;
259
+ private persistDir = './cache';
260
+
261
+ constructor() {
262
+ this.memory = new LRUCache<string, any>({
263
+ max: 1000,
264
+ maxSize: 100 * 1024 * 1024
265
+ });
266
+ }
267
+
268
+ async get(key: string): Promise<any | undefined> {
269
+ // L1: Memory
270
+ const memValue = this.memory.get(key);
271
+ if (memValue) return memValue;
272
+
273
+ // L2: Disk
274
+ try {
275
+ const filePath = `${this.persistDir}/${key}.json`;
276
+ const data = await fs.readFile(filePath, 'utf8');
277
+ const value = JSON.parse(data);
278
+
279
+ // Promote to memory
280
+ this.memory.set(key, value);
281
+ return value;
282
+ } catch {
283
+ return undefined;
284
+ }
285
+ }
286
+
287
+ async set(key: string, value: any): Promise<void> {
288
+ this.memory.set(key, value);
289
+
290
+ // Persist to disk
291
+ const filePath = `${this.persistDir}/${key}.json`;
292
+ await fs.mkdir(this.persistDir, { recursive: true });
293
+ await fs.writeFile(filePath, JSON.stringify(value));
294
+ }
295
+ }
296
+ ```
297
+
298
+ ---
299
+
300
+ ## Usage Patterns
301
+
302
+ ### Pattern 1: Basic Fetch (Recommended)
303
+
304
+ ```typescript
305
+ async function fetchWithCache(
306
+ prompt: string,
307
+ cache: LRUCache<string, string>
308
+ ): Promise<string> {
309
+ const key = createHash('sha256').update(prompt).digest('hex');
310
+
311
+ return cache.fetch(
312
+ key,
313
+ async () => {
314
+ // Only executes on cache miss
315
+ const response = await callLLM(prompt);
316
+ return response;
317
+ },
318
+ {
319
+ ttl: 24 * 3600 * 1000 // Per-item override
320
+ }
321
+ );
322
+ }
323
+
324
+ async function callLLM(prompt: string): Promise<string> {
325
+ // Your LLM API call
326
+ return 'LLM response...';
327
+ }
328
+ ```
329
+
330
+ ### Pattern 2: Conditional Fetch
331
+
332
+ ```typescript
333
+ async function fetchWithExpiry(
334
+ key: string,
335
+ fetchFn: () => Promise<any>,
336
+ cache: LRUCache<string, any>,
337
+ forceRefresh = false
338
+ ): Promise<any> {
339
+ if (forceRefresh) {
340
+ // Force refresh even if cached
341
+ const value = await fetchFn();
342
+ cache.set(key, value);
343
+ return value;
344
+ }
345
+
346
+ return cache.fetch(key, fetchFn);
347
+ }
348
+
349
+ // Usage
350
+ const result = await fetchWithExpiry(
351
+ key,
352
+ async () => expensiveOperation(),
353
+ cache,
354
+ false // Set true to force refresh
355
+ );
356
+ ```
357
+
358
+ ### Pattern 3: Batch Operations
359
+
360
+ ```typescript
361
+ async function cacheBatchLookups(
362
+ prompts: string[],
363
+ cache: LRUCache<string, string>
364
+ ): Promise<Map<string, string>> {
365
+ const results = new Map<string, string>();
366
+
367
+ // Process in parallel with cache deduplication
368
+ await Promise.all(
369
+ prompts.map(async (prompt) => {
370
+ const key = createHash('sha256').update(prompt).digest('hex');
371
+
372
+ const response = await cache.fetch(
373
+ key,
374
+ async () => callLLM(prompt)
375
+ );
376
+
377
+ results.set(prompt, response);
378
+ })
379
+ );
380
+
381
+ return results;
382
+ }
383
+ ```
384
+
385
+ ### Pattern 4: Stale-While-Revalidate Pattern
386
+
387
+ ```typescript
388
+ async function staleWhileRevalidate(
389
+ key: string,
390
+ cache: LRUCache<string, any>,
391
+ fetchFn: () => Promise<any>
392
+ ): Promise<any> {
393
+ // Return stale value immediately if available
394
+ const cached = cache.get(key);
395
+ if (cached) {
396
+ // Refresh in background
397
+ fetchFn().then((fresh) => {
398
+ cache.set(key, fresh);
399
+ }).catch(console.error);
400
+
401
+ return cached;
402
+ }
403
+
404
+ // No cached value, wait for fetch
405
+ return fetchFn().then((value) => {
406
+ cache.set(key, value);
407
+ return value;
408
+ });
409
+ }
410
+ ```
411
+
412
+ ### Pattern 5: Cache Warming
413
+
414
+ ```typescript
415
+ async function warmCache(
416
+ cache: LRUCache<string, any>,
417
+ prompts: string[]
418
+ ): Promise<void> {
419
+ console.log(`Warming cache with ${prompts.length} entries...`);
420
+
421
+ for (const prompt of prompts) {
422
+ const key = createHash('sha256').update(prompt).digest('hex');
423
+
424
+ try {
425
+ await cache.fetch(
426
+ key,
427
+ async () => callLLM(prompt),
428
+ { ttl: 7 * 24 * 3600 * 1000 } // 7 days
429
+ );
430
+ } catch (error) {
431
+ console.error(`Failed to warm cache for prompt: ${prompt}`, error);
432
+ }
433
+ }
434
+
435
+ console.log('Cache warming complete');
436
+ }
437
+
438
+ // Usage
439
+ const commonPrompts = [
440
+ 'What is AI?',
441
+ 'Explain machine learning',
442
+ 'Define neural networks'
443
+ ];
444
+
445
+ await warmCache(cache, commonPrompts);
446
+ ```
447
+
448
+ ---
449
+
450
+ ## Testing Patterns
451
+
452
+ ### Pattern 1: Cache Hit/Miss Testing
453
+
454
+ ```typescript
455
+ import { describe, it, expect } from 'vitest';
456
+
457
+ describe('LLM Cache', () => {
458
+ let cache: LRUCache<string, string>;
459
+
460
+ beforeEach(() => {
461
+ cache = new LRUCache({ max: 100 });
462
+ });
463
+
464
+ it('should have cache hit on repeated prompt', async () => {
465
+ const prompt = 'What is AI?';
466
+ let callCount = 0;
467
+
468
+ const fetchFn = async () => {
469
+ callCount++;
470
+ return 'AI is...';
471
+ };
472
+
473
+ // First call - miss
474
+ const result1 = await cache.fetch(
475
+ createHash('sha256').update(prompt).digest('hex'),
476
+ fetchFn
477
+ );
478
+
479
+ // Second call - hit
480
+ const result2 = await cache.fetch(
481
+ createHash('sha256').update(prompt).digest('hex'),
482
+ fetchFn
483
+ );
484
+
485
+ expect(result1).toBe(result2);
486
+ expect(callCount).toBe(1); // Only called once
487
+ });
488
+
489
+ it('should evict LRU items', () => {
490
+ const cache = new LRUCache({ max: 2 });
491
+
492
+ cache.set('key1', 'value1');
493
+ cache.set('key2', 'value2');
494
+ cache.set('key3', 'value3');
495
+
496
+ expect(cache.get('key1')).toBeUndefined(); // Evicted
497
+ expect(cache.get('key2')).toBe('value2');
498
+ expect(cache.get('key3')).toBe('value3');
499
+ });
500
+ });
501
+ ```
502
+
503
+ ### Pattern 2: Key Generation Testing
504
+
505
+ ```typescript
506
+ describe('Cache Key Generation', () => {
507
+ it('should produce same key for equivalent inputs', () => {
508
+ const obj1 = { a: 1, b: 2, c: 3 };
509
+ const obj2 = { c: 3, b: 2, a: 1 };
510
+
511
+ const key1 = objectKeyHash(obj1);
512
+ const key2 = objectKeyHash(obj2);
513
+
514
+ expect(key1).toBe(key2);
515
+ });
516
+
517
+ it('should handle circular references', () => {
518
+ const obj: any = { value: 42 };
519
+ obj.self = obj;
520
+
521
+ expect(() => {
522
+ objectKeyHash(obj);
523
+ }).not.toThrow();
524
+ });
525
+
526
+ it('should produce different keys for different inputs', () => {
527
+ const key1 = objectKeyHash({ prompt: 'hello' });
528
+ const key2 = objectKeyHash({ prompt: 'world' });
529
+
530
+ expect(key1).not.toBe(key2);
531
+ });
532
+ });
533
+ ```
534
+
535
+ ### Pattern 3: Performance Testing
536
+
537
+ ```typescript
538
+ import { performance } from 'node:perf_hooks';
539
+
540
+ describe('Cache Performance', () => {
541
+ it('should handle 10k cache hits in < 100ms', () => {
542
+ const cache = new LRUCache<string, string>({ max: 1000 });
543
+ cache.set('key', 'value');
544
+
545
+ const start = performance.now();
546
+
547
+ for (let i = 0; i < 10000; i++) {
548
+ cache.get('key');
549
+ }
550
+
551
+ const elapsed = performance.now() - start;
552
+ expect(elapsed).toBeLessThan(100);
553
+ });
554
+
555
+ it('should deterministically stringify in < 1ms', () => {
556
+ const obj = {
557
+ messages: Array(10).fill({ role: 'user', content: 'x'.repeat(100) }),
558
+ model: 'gpt-4',
559
+ temperature: 0.7
560
+ };
561
+
562
+ const start = performance.now();
563
+
564
+ for (let i = 0; i < 1000; i++) {
565
+ safeStringify(obj);
566
+ }
567
+
568
+ const elapsed = performance.now() - start;
569
+ expect(elapsed).toBeLessThan(1000); // 1000 iterations < 1 second
570
+ });
571
+ });
572
+ ```
573
+
574
+ ---
575
+
576
+ ## Monitoring Patterns
577
+
578
+ ### Pattern 1: Basic Metrics Collection
579
+
580
+ ```typescript
581
+ class CacheMetrics {
582
+ hits = 0;
583
+ misses = 0;
584
+ latencies: number[] = [];
585
+
586
+ recordHit(latency: number): void {
587
+ this.hits++;
588
+ this.latencies.push(latency);
589
+ this.trimLatencies();
590
+ }
591
+
592
+ recordMiss(latency: number): void {
593
+ this.misses++;
594
+ this.latencies.push(latency);
595
+ this.trimLatencies();
596
+ }
597
+
598
+ private trimLatencies(): void {
599
+ if (this.latencies.length > 10000) {
600
+ this.latencies = this.latencies.slice(-5000);
601
+ }
602
+ }
603
+
604
+ getStats() {
605
+ const total = this.hits + this.misses;
606
+ const avgLatency = this.latencies.length > 0
607
+ ? this.latencies.reduce((a, b) => a + b, 0) / this.latencies.length
608
+ : 0;
609
+
610
+ return {
611
+ hits: this.hits,
612
+ misses: this.misses,
613
+ hitRate: total > 0 ? ((this.hits / total) * 100).toFixed(2) + '%' : '0%',
614
+ avgLatency: avgLatency.toFixed(3) + ' ms',
615
+ p95Latency: this.percentile(95) + ' ms',
616
+ p99Latency: this.percentile(99) + ' ms'
617
+ };
618
+ }
619
+
620
+ private percentile(p: number): string {
621
+ const sorted = [...this.latencies].sort((a, b) => a - b);
622
+ const idx = Math.ceil((p / 100) * sorted.length) - 1;
623
+ return sorted[idx]?.toFixed(3) || '0';
624
+ }
625
+ }
626
+ ```
627
+
628
+ ### Pattern 2: Periodic Logging
629
+
630
+ ```typescript
631
+ function setupCacheMonitoring(
632
+ cache: LRUCache<string, any>,
633
+ metrics: CacheMetrics,
634
+ intervalMs = 60000
635
+ ): () => void {
636
+ const timer = setInterval(() => {
637
+ const stats = metrics.getStats();
638
+ console.log('[Cache Status]', {
639
+ timestamp: new Date().toISOString(),
640
+ size: cache.size,
641
+ ...stats
642
+ });
643
+ }, intervalMs);
644
+
645
+ return () => clearInterval(timer);
646
+ }
647
+
648
+ // Usage
649
+ const metrics = new CacheMetrics();
650
+ const stopMonitoring = setupCacheMonitoring(cache, metrics, 30000);
651
+
652
+ // Later
653
+ stopMonitoring();
654
+ ```
655
+
656
+ ### Pattern 3: Alert on Low Hit Rate
657
+
658
+ ```typescript
659
+ function monitorHitRate(
660
+ metrics: CacheMetrics,
661
+ minHitRate = 0.3, // 30% minimum
662
+ checkIntervalMs = 60000
663
+ ): () => void {
664
+ const timer = setInterval(() => {
665
+ const stats = metrics.getStats();
666
+ const hitRate = parseFloat(stats.hitRate);
667
+
668
+ if (hitRate < minHitRate * 100) {
669
+ console.warn(
670
+ `⚠️ Low cache hit rate: ${stats.hitRate} (threshold: ${minHitRate * 100}%)`
671
+ );
672
+ }
673
+ }, checkIntervalMs);
674
+
675
+ return () => clearInterval(timer);
676
+ }
677
+ ```
678
+
679
+ ### Pattern 4: Export Metrics to JSON
680
+
681
+ ```typescript
682
+ async function exportMetrics(
683
+ metrics: CacheMetrics,
684
+ filePath: string
685
+ ): Promise<void> {
686
+ const stats = metrics.getStats();
687
+ const data = JSON.stringify(stats, null, 2);
688
+
689
+ await fs.promises.writeFile(filePath, data, 'utf8');
690
+ console.log(`Metrics exported to ${filePath}`);
691
+ }
692
+
693
+ // Usage
694
+ await exportMetrics(metrics, './cache-metrics.json');
695
+ ```
696
+
697
+ ---
698
+
699
+ ## Edge Cases and Solutions
700
+
701
+ ### Handling Large Prompts
702
+
703
+ ```typescript
704
+ // For very large prompts, use streaming hash
705
+ async function hashLargePrompt(prompt: string): Promise<string> {
706
+ const hash = createHash('sha256');
707
+ const chunkSize = 65536; // 64 KB
708
+
709
+ for (let i = 0; i < prompt.length; i += chunkSize) {
710
+ hash.update(prompt.slice(i, i + chunkSize));
711
+ }
712
+
713
+ return hash.digest('hex');
714
+ }
715
+ ```
716
+
717
+ ### Handling Special Characters
718
+
719
+ ```typescript
720
+ // Ensure UTF-8 encoding for consistent hashing
721
+ function hashWithEncoding(input: string): string {
722
+ const buffer = Buffer.from(input, 'utf8');
723
+ const hash = createHash('sha256');
724
+ hash.update(buffer);
725
+ return hash.digest('hex');
726
+ }
727
+ ```
728
+
729
+ ### Handling Null/Undefined Values
730
+
731
+ ```typescript
732
+ function safeKeyHash(value: any): string {
733
+ if (value === null || value === undefined) {
734
+ return createHash('sha256').update('null').digest('hex');
735
+ }
736
+
737
+ const str = safeStringify(value);
738
+ const hash = createHash('sha256');
739
+ hash.update(str);
740
+ return hash.digest('hex');
741
+ }
742
+ ```
743
+
744
+ ---
745
+
746
+ ## Complete Integration Example
747
+
748
+ ```typescript
749
+ // cache.service.ts
750
+ import { LRUCache } from 'lru-cache';
751
+ import { createHash } from 'node:crypto';
752
+ import safeStringify from 'safe-stable-stringify';
753
+
754
+ export interface CacheConfig {
755
+ maxItems?: number;
756
+ maxSizeMB?: number;
757
+ ttlHours?: number;
758
+ }
759
+
760
+ export class LLMCacheService {
761
+ private cache: LRUCache<string, any>;
762
+ private metrics = {
763
+ hits: 0,
764
+ misses: 0,
765
+ latencies: [] as number[]
766
+ };
767
+
768
+ constructor(config: CacheConfig = {}) {
769
+ const {
770
+ maxItems = 5000,
771
+ maxSizeMB = 500,
772
+ ttlHours = 24
773
+ } = config;
774
+
775
+ this.cache = new LRUCache({
776
+ max: maxItems,
777
+ maxSize: maxSizeMB * 1024 * 1024,
778
+ sizeCalculation: (val) => {
779
+ const json = JSON.stringify(val);
780
+ return Buffer.byteLength(json, 'utf8') + 100;
781
+ },
782
+ ttl: ttlHours * 3600 * 1000,
783
+ updateAgeOnGet: true
784
+ });
785
+ }
786
+
787
+ async fetch<T>(
788
+ input: Record<string, any>,
789
+ fetcher: () => Promise<T>
790
+ ): Promise<T> {
791
+ const start = performance.now();
792
+ const key = this.generateKey(input);
793
+
794
+ const result = await this.cache.fetch(key, fetcher);
795
+
796
+ const latency = performance.now() - start;
797
+ const cached = this.cache.get(key) !== undefined;
798
+
799
+ if (cached) {
800
+ this.metrics.hits++;
801
+ } else {
802
+ this.metrics.misses++;
803
+ }
804
+ this.metrics.latencies.push(latency);
805
+
806
+ return result;
807
+ }
808
+
809
+ private generateKey(input: Record<string, any>): string {
810
+ const normalized = safeStringify(input);
811
+ const hash = createHash('sha256');
812
+ hash.update(normalized);
813
+ return hash.digest('hex');
814
+ }
815
+
816
+ getMetrics() {
817
+ const total = this.metrics.hits + this.metrics.misses;
818
+ return {
819
+ hits: this.metrics.hits,
820
+ misses: this.metrics.misses,
821
+ hitRate: total > 0 ? (this.metrics.hits / total * 100).toFixed(2) : '0',
822
+ size: this.cache.size
823
+ };
824
+ }
825
+
826
+ clear(): void {
827
+ this.cache.clear();
828
+ }
829
+ }
830
+
831
+ // Usage in your application
832
+ const cacheService = new LLMCacheService({
833
+ maxItems: 5000,
834
+ maxSizeMB: 500,
835
+ ttlHours: 24
836
+ });
837
+
838
+ // Use in your LLM service
839
+ const response = await cacheService.fetch(
840
+ {
841
+ model: 'gpt-4',
842
+ prompt: 'What is AI?',
843
+ temperature: 0.7
844
+ },
845
+ async () => {
846
+ return callOpenAIAPI(...);
847
+ }
848
+ );
849
+
850
+ // Monitor
851
+ console.log(cacheService.getMetrics());
852
+ ```
853
+
854
+ ---
855
+
856
+ **Document Version:** 1.0
857
+ **Last Updated:** 2025-12-08