adaptive-memory-multi-model-router 2.16.0 → 2.16.2
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.
- package/.github/workflows/adapters-ci.yml +142 -0
- package/.github/workflows/auto-submit-sitemap.yml +41 -0
- package/.github/workflows/ci.yml +2 -5
- package/.github/workflows/mcp-pypi-publish.yml +34 -0
- package/.github/workflows/pypi-publish.yml +146 -0
- package/.github/workflows/tmlpd-publish.yml +23 -0
- package/README.md +245 -148
- package/RELEASE_v2.16.0.md +149 -0
- package/TECHNICAL_README.md +253 -0
- package/adapters/README.md +36 -0
- package/adapters/__init__.py +25 -0
- package/adapters/a3m_adapter/__init__.py +51 -0
- package/adapters/a3m_adapter/adapter/__init__.py +22 -0
- package/adapters/a3m_adapter/adapter/autogen.py +169 -0
- package/adapters/a3m_adapter/adapter/config.py +100 -0
- package/adapters/a3m_adapter/adapter/haystack.py +197 -0
- package/adapters/a3m_adapter/adapter/langchain.py +155 -0
- package/adapters/a3m_adapter/adapter/langgraph.py +196 -0
- package/adapters/a3m_adapter/adapter/llamaindex.py +162 -0
- package/adapters/a3m_adapter/adapter/pinecone.py +217 -0
- package/adapters/a3m_adapter/adapter/vercel.py +188 -0
- package/adapters/a3m_adapter/tests/__init__.py +1 -0
- package/adapters/a3m_adapter/tests/test_adapters.py +118 -0
- package/adapters/a3m_adapter/tests/test_integration.py +80 -0
- package/adapters/requirements-dev.txt +6 -0
- package/adapters/requirements.txt +4 -0
- package/adapters/setup.py +23 -0
- package/demo.py +251 -0
- package/discoverability-diagnosis.md +280 -0
- package/dist/analytics/costAnalytics.d.ts.map +1 -1
- package/dist/benchmark/reproducible.d.ts.map +1 -1
- package/dist/cache/semanticCache.d.ts.map +1 -1
- package/dist/cli/setupWizard.d.ts +257 -50
- package/dist/cli/setupWizard.d.ts.map +1 -1
- package/dist/cli/setupWizard.js +419 -109
- package/dist/cli/setupWizard.js.map +1 -1
- package/dist/cli/tui.d.ts +6 -0
- package/dist/cli/tui.js +96 -67
- package/dist/cli/tui.js.map +1 -0
- package/dist/cli.js +9 -0
- package/dist/cost/budgetEnforcer.d.ts.map +1 -1
- package/dist/cost/costTracker.d.ts.map +1 -1
- package/dist/ensemble/multiRoundDialog.d.ts.map +1 -1
- package/dist/ensemble/shapleyValue.d.ts.map +1 -1
- package/dist/ensemble.d.ts +1 -1
- package/dist/ensemble.js +141 -0
- package/dist/integrations/langchainAdapter.d.ts.map +1 -1
- package/dist/integrations/langchainAdapter.js +3 -3
- package/dist/integrations/langchainAdapter.js.map +1 -1
- package/dist/integrations/oauth.d.ts.map +1 -1
- package/dist/integrations/scienceAdapter.d.ts.map +1 -1
- package/dist/memory/autoFetch.d.ts.map +1 -1
- package/dist/memory/hybridMemory.d.ts.map +1 -1
- package/dist/memory/memoryTree.d.ts.map +1 -1
- package/dist/memory/obsidianVault.d.ts.map +1 -1
- package/dist/memory/reasoningBank.d.ts.map +1 -1
- package/dist/observability/metrics.d.ts.map +1 -1
- package/dist/observability/tracer.d.ts.map +1 -1
- package/dist/providers/providerConfig.d.ts.map +1 -1
- package/dist/providers/providerConfig.js +32 -17
- package/dist/providers/providerConfig.js.map +1 -1
- package/dist/routing/advancedRouter.d.ts.map +1 -1
- package/dist/routing/advancedRouter.js +106 -14
- package/dist/routing/advancedRouter.js.map +1 -1
- package/dist/routing/providerHealth.d.ts.map +1 -1
- package/dist/routing/providerRetry.d.ts.map +1 -1
- package/dist/routing/shadowSampler.d.ts.map +1 -0
- package/dist/routing/shadowSampler.js.map +1 -1
- package/dist/security/guardrails.d.ts.map +1 -1
- package/dist/server/handlers/chatHandler.d.ts.map +1 -1
- package/dist/server/handlers/completionsHandler.d.ts.map +1 -1
- package/dist/server/handlers/embeddingsHandler.d.ts.map +1 -1
- package/dist/server/handlers/healthHandler.d.ts.map +1 -1
- package/dist/server/handlers/metricsHandler.d.ts.map +1 -1
- package/dist/server/handlers/modelsHandler.d.ts.map +1 -1
- package/dist/server/metrics.d.ts.map +1 -1
- package/dist/server/proxyServer.d.ts.map +1 -1
- package/dist/server/router.d.ts.map +1 -1
- package/dist/server/state.d.ts.map +1 -1
- package/dist/skills/__tests__/skill_manager.test.js +5 -265
- package/dist/skills/__tests__/skill_manager.test.js.map +1 -1
- package/dist/utils/tokenUtils.d.ts.map +1 -1
- package/docker-compose.yml +84 -60
- package/docs/ARTICLE_Biology_Inspired_Routing.md +208 -0
- package/docs/ARTICLE_Master.md +78 -0
- package/docs/ARTICLE_Master_CN.md +78 -0
- package/docs/ARTICLE_OpenRouter_Stripe.md +140 -0
- package/docs/DEVPTO_ARTICLE.md +84 -0
- package/docs/HUMAN_STYLE_GUIDE.md +75 -0
- package/docs/IMPRINT_PLAN.md +88 -0
- package/docs/OPENROUTER_ALTERNATIVE.md +184 -0
- package/docs/SOCIAL_CAMPAIGN.md +316 -0
- package/docs/anthropic.html +45 -0
- package/docs/best-llm-routers-2025.html +157 -0
- package/docs/cerebras.html +43 -0
- package/docs/cli-cheatsheet.md +286 -212
- package/docs/comparison.md +2 -2
- package/docs/deepseek.html +44 -0
- package/docs/google.html +47 -0
- package/docs/groq.html +44 -0
- package/docs/llms-full.txt +360 -138
- package/docs/llms.txt +70 -71
- package/docs/mistral.html +43 -0
- package/docs/ollama.html +50 -0
- package/docs/openai.html +57 -0
- package/docs/sitemap.xml +69 -57
- package/docs-site/blog/best-llm-routers-2025.html +157 -0
- package/docs-site/index.html +68 -9
- package/docs-site/providers/anthropic.html +45 -0
- package/docs-site/providers/cerebras.html +43 -0
- package/docs-site/providers/deepseek.html +44 -0
- package/docs-site/providers/google.html +47 -0
- package/docs-site/providers/groq.html +44 -0
- package/docs-site/providers/index.html +41 -0
- package/docs-site/providers/mistral.html +43 -0
- package/docs-site/providers/ollama.html +50 -0
- package/docs-site/providers/openai.html +57 -0
- package/docs-site/sitemap.xml +69 -0
- package/llms.txt +70 -62
- package/package.json +44 -182
- package/packages/agentkit-adapter/LICENSE +21 -0
- package/packages/agentkit-adapter/README.md +126 -0
- package/packages/agentkit-adapter/examples/agentkit-example.ts +139 -0
- package/packages/agentkit-adapter/package.json +57 -0
- package/packages/agentkit-adapter/src/adapter.ts +381 -0
- package/packages/agentkit-adapter/src/index.ts +36 -0
- package/packages/agentkit-adapter/src/types.ts +105 -0
- package/packages/agentkit-adapter/src/util.ts +13 -0
- package/packages/agentkit-adapter/tsconfig.json +22 -0
- package/prometheus.yml +8 -0
- package/python/README.md +35 -81
- package/python/a3m/__init__.py +32 -3
- package/python/a3m/adapters/__init__.py +21 -0
- package/python/a3m/adapters/langchain.py +190 -0
- package/python/a3m/adapters/llamaindex.py +249 -0
- package/python/a3m/adapters/qdrant.py +240 -0
- package/python/a3m/adapters/weaviate.py +263 -0
- package/python/a3m/client.py +5 -0
- package/python/build_verify.sh +32 -0
- package/python/mcp-server/README.md +172 -0
- package/python/mcp-server/a3m_mcp/__init__.py +25 -0
- package/python/mcp-server/a3m_mcp/__main__.py +15 -0
- package/python/mcp-server/a3m_mcp/server.py +205 -0
- package/python/mcp-server/pyproject.toml +24 -0
- package/python/pyproject.toml +60 -6
- package/python/setup.py +3 -28
- package/scripts/submit-sitemap.sh +52 -0
- package/src/__types__/registry.d.ts +14 -0
- package/src/cli/setupWizard.ts +443 -112
- package/src/cli/tui.ts +159 -0
- package/src/ensemble.ts +154 -1
- package/src/integrations/langchainAdapter.ts +2 -2
- package/src/providers/providerConfig.ts +32 -17
- package/src/providers/registry.js +27 -0
- package/src/routing/advancedRouter.ts +99 -14
- package/src/routing/shadowSampler.ts +1 -1
- package/test-install/package.json +12 -0
- package/tests/tsconfig.json +0 -1
- package/tmlpd-pi-extension/README.md +105 -44
- package/tmlpd-pi-extension/docs/demo.svg +33 -0
- package/tmlpd-pi-extension/package.json +35 -106
- package/tmlpd-pi-extension/src/tokenOptimization/contextStratifier.ts +163 -0
- package/tmlpd-pi-extension/src/tokenOptimization/fetchOnceLocal.ts +136 -0
- package/tmlpd-pi-extension/src/tokenOptimization/index.ts +197 -0
- package/tmlpd-pi-extension/src/tokenOptimization/interAgentCompression.ts +157 -0
- package/tmlpd-pi-extension/src/tokenOptimization/schemaContract.ts +101 -0
- package/tmlpd-pi-extension/src/tokenOptimization/semanticCache.ts +248 -0
- package/tmlpd-pi-extension/src/tokenOptimization/tokenAwareFallback.ts +192 -0
- package/tmlpd-pi-extension/test/verify.js +21 -0
- package/tsconfig.build.json +3 -2
- package/packages/a3m-vercel-ai/dist/a3m-language-model.d.ts +0 -12
- package/packages/a3m-vercel-ai/dist/a3m-language-model.d.ts.map +0 -1
- package/packages/a3m-vercel-ai/dist/a3m-language-model.js +0 -289
- package/packages/a3m-vercel-ai/dist/a3m-language-model.js.map +0 -1
- package/packages/a3m-vercel-ai/dist/index.d.ts +0 -82
- package/packages/a3m-vercel-ai/dist/index.d.ts.map +0 -1
- package/packages/a3m-vercel-ai/dist/index.js +0 -79
- package/packages/a3m-vercel-ai/dist/index.js.map +0 -1
- package/packages/a3m-vercel-ai/dist/types.d.ts +0 -97
- package/packages/a3m-vercel-ai/dist/types.d.ts.map +0 -1
- package/packages/a3m-vercel-ai/dist/types.js +0 -5
- package/packages/a3m-vercel-ai/dist/types.js.map +0 -1
- package/src/skills/__tests__/skill_manager.test.ts +0 -328
- package/tmlpd-pi-extension/dist/cache/prefixCache.d.ts +0 -114
- package/tmlpd-pi-extension/dist/cache/prefixCache.d.ts.map +0 -1
- package/tmlpd-pi-extension/dist/cache/prefixCache.js +0 -285
- package/tmlpd-pi-extension/dist/cache/prefixCache.js.map +0 -1
- package/tmlpd-pi-extension/dist/cache/responseCache.d.ts +0 -58
- package/tmlpd-pi-extension/dist/cache/responseCache.d.ts.map +0 -1
- package/tmlpd-pi-extension/dist/cache/responseCache.js +0 -153
- package/tmlpd-pi-extension/dist/cache/responseCache.js.map +0 -1
- package/tmlpd-pi-extension/dist/cli.js +0 -59
- package/tmlpd-pi-extension/dist/cost/costTracker.d.ts +0 -95
- package/tmlpd-pi-extension/dist/cost/costTracker.d.ts.map +0 -1
- package/tmlpd-pi-extension/dist/cost/costTracker.js +0 -240
- package/tmlpd-pi-extension/dist/cost/costTracker.js.map +0 -1
- package/tmlpd-pi-extension/dist/index.d.ts +0 -723
- package/tmlpd-pi-extension/dist/index.d.ts.map +0 -1
- package/tmlpd-pi-extension/dist/index.js +0 -239
- package/tmlpd-pi-extension/dist/index.js.map +0 -1
- package/tmlpd-pi-extension/dist/memory/episodicMemory.d.ts +0 -82
- package/tmlpd-pi-extension/dist/memory/episodicMemory.d.ts.map +0 -1
- package/tmlpd-pi-extension/dist/memory/episodicMemory.js +0 -145
- package/tmlpd-pi-extension/dist/memory/episodicMemory.js.map +0 -1
- package/tmlpd-pi-extension/dist/orchestration/haloOrchestrator.d.ts +0 -102
- package/tmlpd-pi-extension/dist/orchestration/haloOrchestrator.d.ts.map +0 -1
- package/tmlpd-pi-extension/dist/orchestration/haloOrchestrator.js +0 -207
- package/tmlpd-pi-extension/dist/orchestration/haloOrchestrator.js.map +0 -1
- package/tmlpd-pi-extension/dist/orchestration/mctsWorkflow.d.ts +0 -85
- package/tmlpd-pi-extension/dist/orchestration/mctsWorkflow.d.ts.map +0 -1
- package/tmlpd-pi-extension/dist/orchestration/mctsWorkflow.js +0 -210
- package/tmlpd-pi-extension/dist/orchestration/mctsWorkflow.js.map +0 -1
- package/tmlpd-pi-extension/dist/providers/localProvider.d.ts +0 -102
- package/tmlpd-pi-extension/dist/providers/localProvider.d.ts.map +0 -1
- package/tmlpd-pi-extension/dist/providers/localProvider.js +0 -338
- package/tmlpd-pi-extension/dist/providers/localProvider.js.map +0 -1
- package/tmlpd-pi-extension/dist/providers/registry.d.ts +0 -55
- package/tmlpd-pi-extension/dist/providers/registry.d.ts.map +0 -1
- package/tmlpd-pi-extension/dist/providers/registry.js +0 -138
- package/tmlpd-pi-extension/dist/providers/registry.js.map +0 -1
- package/tmlpd-pi-extension/dist/routing/advancedRouter.d.ts +0 -68
- package/tmlpd-pi-extension/dist/routing/advancedRouter.d.ts.map +0 -1
- package/tmlpd-pi-extension/dist/routing/advancedRouter.js +0 -332
- package/tmlpd-pi-extension/dist/routing/advancedRouter.js.map +0 -1
- package/tmlpd-pi-extension/dist/tools/tmlpdTools.d.ts +0 -101
- package/tmlpd-pi-extension/dist/tools/tmlpdTools.d.ts.map +0 -1
- package/tmlpd-pi-extension/dist/tools/tmlpdTools.js +0 -368
- package/tmlpd-pi-extension/dist/tools/tmlpdTools.js.map +0 -1
- package/tmlpd-pi-extension/dist/utils/batchProcessor.d.ts +0 -96
- package/tmlpd-pi-extension/dist/utils/batchProcessor.d.ts.map +0 -1
- package/tmlpd-pi-extension/dist/utils/batchProcessor.js +0 -170
- package/tmlpd-pi-extension/dist/utils/batchProcessor.js.map +0 -1
- package/tmlpd-pi-extension/dist/utils/compression.d.ts +0 -61
- package/tmlpd-pi-extension/dist/utils/compression.d.ts.map +0 -1
- package/tmlpd-pi-extension/dist/utils/compression.js +0 -281
- package/tmlpd-pi-extension/dist/utils/compression.js.map +0 -1
- package/tmlpd-pi-extension/dist/utils/reliability.d.ts +0 -74
- package/tmlpd-pi-extension/dist/utils/reliability.d.ts.map +0 -1
- package/tmlpd-pi-extension/dist/utils/reliability.js +0 -177
- package/tmlpd-pi-extension/dist/utils/reliability.js.map +0 -1
- package/tmlpd-pi-extension/dist/utils/speculativeDecoding.d.ts +0 -117
- package/tmlpd-pi-extension/dist/utils/speculativeDecoding.d.ts.map +0 -1
- package/tmlpd-pi-extension/dist/utils/speculativeDecoding.js +0 -246
- package/tmlpd-pi-extension/dist/utils/speculativeDecoding.js.map +0 -1
- package/tmlpd-pi-extension/dist/utils/tokenUtils.d.ts +0 -50
- package/tmlpd-pi-extension/dist/utils/tokenUtils.d.ts.map +0 -1
- package/tmlpd-pi-extension/dist/utils/tokenUtils.js +0 -124
- package/tmlpd-pi-extension/dist/utils/tokenUtils.js.map +0 -1
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Token Optimization Module - Phase 1 Implementation
|
|
3
|
+
*
|
|
4
|
+
* 6 patterns from arXiv:2608.17188 - Token Optimization and Context Window Management
|
|
5
|
+
*
|
|
6
|
+
* Patterns:
|
|
7
|
+
* 1. SemanticCache - Embedding-based similarity caching
|
|
8
|
+
* 2. ContextStratifier -分层 context levels
|
|
9
|
+
* 3. TokenAwareFallback - Token-count based model routing
|
|
10
|
+
* 4. SchemaContractor - Schema-referenced prompts
|
|
11
|
+
* 5. FetchOnceProcessor - Fetch once, process locally
|
|
12
|
+
* 6. InterAgentCompressor - Message compression
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
export { SemanticCache, SemanticCacheConfig, SemanticCacheEntry } from "./semanticCache";
|
|
16
|
+
export { ContextStratifier, ContextLevel, ContextStratifierConfig, StratificationResult } from "./contextStratifier";
|
|
17
|
+
export { TokenAwareFallback, TokenAwareFallbackConfig, FallbackDecision, TokenThreshold } from "./tokenAwareFallback";
|
|
18
|
+
export { SchemaContractor, SchemaContractConfig, SchemaInfo } from "./schemaContract";
|
|
19
|
+
export { FetchOnceProcessor, FetchOnceConfig, FetchOnceResult } from "./fetchOnceLocal";
|
|
20
|
+
export { InterAgentCompressor, Message, CompressionConfig, CompressionResult } from "./interAgentCompression";
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Create a fully configured TokenOptimizer with all patterns
|
|
24
|
+
*/
|
|
25
|
+
import { SemanticCache } from "./semanticCache";
|
|
26
|
+
import { ContextStratifier } from "./contextStratifier";
|
|
27
|
+
import { TokenAwareFallback } from "./tokenAwareFallback";
|
|
28
|
+
import { SchemaContractor } from "./schemaContract";
|
|
29
|
+
import { FetchOnceProcessor } from "./fetchOnceLocal";
|
|
30
|
+
import { InterAgentCompressor } from "./interAgentCompression";
|
|
31
|
+
|
|
32
|
+
export interface TokenOptimizerConfig {
|
|
33
|
+
semanticCache?: boolean;
|
|
34
|
+
contextStratification?: boolean;
|
|
35
|
+
tokenAwareFallback?: boolean;
|
|
36
|
+
schemaContraction?: boolean;
|
|
37
|
+
fetchOnce?: boolean;
|
|
38
|
+
agentCompression?: boolean;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export class TokenOptimizer {
|
|
42
|
+
semanticCache: SemanticCache;
|
|
43
|
+
contextStratifier: ContextStratifier;
|
|
44
|
+
tokenAwareFallback: TokenAwareFallback;
|
|
45
|
+
schemaContractor: SchemaContractor;
|
|
46
|
+
fetchOnceProcessor: FetchOnceProcessor;
|
|
47
|
+
agentCompressor: InterAgentCompressor;
|
|
48
|
+
|
|
49
|
+
private config: TokenOptimizerConfig;
|
|
50
|
+
|
|
51
|
+
constructor(config: TokenOptimizerConfig = {}) {
|
|
52
|
+
this.config = config;
|
|
53
|
+
|
|
54
|
+
this.semanticCache = new SemanticCache({
|
|
55
|
+
enabled: config.semanticCache ?? true,
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
this.contextStratifier = new ContextStratifier();
|
|
59
|
+
|
|
60
|
+
this.tokenAwareFallback = new TokenAwareFallback();
|
|
61
|
+
|
|
62
|
+
this.schemaContractor = new SchemaContractor();
|
|
63
|
+
|
|
64
|
+
this.fetchOnceProcessor = new FetchOnceProcessor();
|
|
65
|
+
|
|
66
|
+
this.agentCompressor = new InterAgentCompressor();
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Optimize a query before sending to LLM
|
|
71
|
+
*/
|
|
72
|
+
async optimizeQuery(
|
|
73
|
+
query: string,
|
|
74
|
+
history?: Array<{ role: string; content: string }>
|
|
75
|
+
): Promise<{
|
|
76
|
+
query: string;
|
|
77
|
+
history: Array<{ role: string; content: string }>;
|
|
78
|
+
contextLevel: string;
|
|
79
|
+
recommendedModel: string;
|
|
80
|
+
cacheHit: boolean;
|
|
81
|
+
cachedResponse?: string;
|
|
82
|
+
}> {
|
|
83
|
+
// 1. Check semantic cache
|
|
84
|
+
const cacheResult = await this.semanticCache.get(query);
|
|
85
|
+
if (cacheResult) {
|
|
86
|
+
return {
|
|
87
|
+
query,
|
|
88
|
+
history: [],
|
|
89
|
+
contextLevel: "CACHED",
|
|
90
|
+
recommendedModel: "cached",
|
|
91
|
+
cacheHit: true,
|
|
92
|
+
cachedResponse: cacheResult.response,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// 2. Classify context level
|
|
97
|
+
const stratification = this.contextStratifier.classify(query, history);
|
|
98
|
+
|
|
99
|
+
// 3. Get token-aware model recommendation
|
|
100
|
+
const totalTokens = this.tokenAwareFallback.estimateTotalTokens(query, history);
|
|
101
|
+
const modelDecision = this.tokenAwareFallback.selectModel(totalTokens);
|
|
102
|
+
|
|
103
|
+
// 4. Stratify history
|
|
104
|
+
const stratifiedHistory = this.contextStratifier.stratifyHistory(
|
|
105
|
+
history as Array<{ role: string; content: string }>,
|
|
106
|
+
stratification.level
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
return {
|
|
110
|
+
query,
|
|
111
|
+
history: stratifiedHistory,
|
|
112
|
+
contextLevel: stratification.level,
|
|
113
|
+
recommendedModel: modelDecision.targetModel,
|
|
114
|
+
cacheHit: false,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Record a query-response pair in cache
|
|
120
|
+
*/
|
|
121
|
+
async recordResponse(
|
|
122
|
+
query: string,
|
|
123
|
+
response: string,
|
|
124
|
+
options: {
|
|
125
|
+
model?: string;
|
|
126
|
+
tokens?: number;
|
|
127
|
+
cost?: number;
|
|
128
|
+
} = {}
|
|
129
|
+
): Promise<void> {
|
|
130
|
+
await this.semanticCache.set(query, response, {
|
|
131
|
+
model: options.model,
|
|
132
|
+
tokensSaved: options.tokens,
|
|
133
|
+
costSaved: options.cost,
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Get optimization statistics
|
|
139
|
+
*/
|
|
140
|
+
getStats() {
|
|
141
|
+
return {
|
|
142
|
+
semanticCache: this.semanticCache.getStats(),
|
|
143
|
+
tokenAwareFallback: {
|
|
144
|
+
thresholds: this.tokenAwareFallback.getThresholds(),
|
|
145
|
+
},
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Helper to estimate token savings across all patterns
|
|
152
|
+
*/
|
|
153
|
+
export function estimateTotalSavings(
|
|
154
|
+
patterns: ("semantic" | "context" | "fallback" | "schema" | "fetchOnce" | "compression")[],
|
|
155
|
+
queryTokens: number,
|
|
156
|
+
responseTokens: number
|
|
157
|
+
): {
|
|
158
|
+
tokensSaved: number;
|
|
159
|
+
percentReduction: string;
|
|
160
|
+
patterns: string[];
|
|
161
|
+
} {
|
|
162
|
+
let totalTokens = queryTokens + responseTokens;
|
|
163
|
+
let saved = 0;
|
|
164
|
+
const appliedPatterns: string[] = [];
|
|
165
|
+
|
|
166
|
+
if (patterns.includes("semantic")) {
|
|
167
|
+
// ~20% cache hit rate after warmup
|
|
168
|
+
saved += totalTokens * 0.2;
|
|
169
|
+
appliedPatterns.push("Semantic Caching (-20%)");
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (patterns.includes("context")) {
|
|
173
|
+
// ~30% reduction from context stratification
|
|
174
|
+
saved += totalTokens * 0.3;
|
|
175
|
+
appliedPatterns.push("Context Stratification (-30%)");
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (patterns.includes("fallback")) {
|
|
179
|
+
// ~40% savings from cheap model routing
|
|
180
|
+
saved += totalTokens * 0.4;
|
|
181
|
+
appliedPatterns.push("Token-Aware Fallback (-40%)");
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (patterns.includes("compression")) {
|
|
185
|
+
// ~15% from message compression
|
|
186
|
+
saved += totalTokens * 0.15;
|
|
187
|
+
appliedPatterns.push("Inter-Agent Compression (-15%)");
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const percentReduction = totalTokens > 0 ? ((saved / totalTokens) * 100).toFixed(1) : "0%";
|
|
191
|
+
|
|
192
|
+
return {
|
|
193
|
+
tokensSaved: Math.round(saved),
|
|
194
|
+
percentReduction: percentReduction + "%",
|
|
195
|
+
patterns: appliedPatterns,
|
|
196
|
+
};
|
|
197
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Inter-Agent Communication Compression - Token Optimization Pattern #6
|
|
3
|
+
*
|
|
4
|
+
* Compress messages between agents to reduce token usage.
|
|
5
|
+
* Uses summarization for long messages, preserves key info for short ones.
|
|
6
|
+
*
|
|
7
|
+
* Based on: arXiv:2608.17188 - Token Optimization and Context Window Management
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export interface Message {
|
|
11
|
+
role: "user" | "assistant" | "system" | "tool";
|
|
12
|
+
content: string;
|
|
13
|
+
metadata?: Record<string, unknown>;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface CompressionConfig {
|
|
17
|
+
maxMessageLength: number;
|
|
18
|
+
summarizeThreshold: number;
|
|
19
|
+
preserveKeywords: string[];
|
|
20
|
+
summaryModel?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface CompressionResult {
|
|
24
|
+
messages: Message[];
|
|
25
|
+
originalTokens: number;
|
|
26
|
+
compressedTokens: number;
|
|
27
|
+
compressionRatio: number;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Keywords that should be preserved in compression
|
|
32
|
+
*/
|
|
33
|
+
const PRESERVE_KEYWORDS = [
|
|
34
|
+
"error", "warning", "fail", "success", "api", "url",
|
|
35
|
+
"id", "token", "key", "config", "path", "file",
|
|
36
|
+
"function", "class", "method", "variable", "import",
|
|
37
|
+
"json", "xml", "html", "css", "sql"
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Simple summarizer (in production, use actual LLM)
|
|
42
|
+
*/
|
|
43
|
+
function simpleSummarize(text: string, maxLength: number): string {
|
|
44
|
+
if (text.length <= maxLength) return text;
|
|
45
|
+
|
|
46
|
+
// Truncate and add indicator
|
|
47
|
+
const summary = text.substring(0, maxLength - 20);
|
|
48
|
+
return summary + "... [truncated]";
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export class InterAgentCompressor {
|
|
52
|
+
private config: CompressionConfig;
|
|
53
|
+
|
|
54
|
+
constructor(config: Partial<CompressionConfig> = {}) {
|
|
55
|
+
this.config = {
|
|
56
|
+
maxMessageLength: config.maxMessageLength ?? 500,
|
|
57
|
+
summarizeThreshold: config.summarizeThreshold ?? 300,
|
|
58
|
+
preserveKeywords: config.preserveKeywords ?? PRESERVE_KEYWORDS,
|
|
59
|
+
summaryModel: config.summaryModel ?? "gpt-4o-mini",
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Estimate tokens in text (rough)
|
|
65
|
+
*/
|
|
66
|
+
private estimateTokens(text: string): number {
|
|
67
|
+
return Math.ceil(text.length / 4);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Check if text contains preserved keywords
|
|
72
|
+
*/
|
|
73
|
+
private containsPreservedKeyword(text: string): boolean {
|
|
74
|
+
const lower = text.toLowerCase();
|
|
75
|
+
return this.config.preserveKeywords.some(kw => lower.includes(kw));
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Compress a single message
|
|
80
|
+
*/
|
|
81
|
+
compressMessage(message: Message): Message {
|
|
82
|
+
const content = message.content ?? "";
|
|
83
|
+
const tokens = this.estimateTokens(content);
|
|
84
|
+
|
|
85
|
+
// If below threshold, no compression needed
|
|
86
|
+
if (tokens <= this.config.summarizeThreshold && !this.containsPreservedKeyword(content)) {
|
|
87
|
+
return message;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// If very long, truncate
|
|
91
|
+
if (content.length > this.config.maxMessageLength * 2) {
|
|
92
|
+
return {
|
|
93
|
+
...message,
|
|
94
|
+
content: simpleSummarize(content, this.config.maxMessageLength),
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Otherwise, truncate to threshold
|
|
99
|
+
return {
|
|
100
|
+
...message,
|
|
101
|
+
content: simpleSummarize(content, this.config.maxMessageLength),
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Compress array of messages
|
|
107
|
+
*/
|
|
108
|
+
compress(messages: Message[]): CompressionResult {
|
|
109
|
+
const originalTokens = messages.reduce(
|
|
110
|
+
(sum, msg) => sum + this.estimateTokens(msg.content ?? ""),
|
|
111
|
+
0
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
const compressed = messages.map(msg => this.compressMessage(msg));
|
|
115
|
+
|
|
116
|
+
const compressedTokens = compressed.reduce(
|
|
117
|
+
(sum, msg) => sum + this.estimateTokens(msg.content ?? ""),
|
|
118
|
+
0
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
return {
|
|
122
|
+
messages: compressed,
|
|
123
|
+
originalTokens,
|
|
124
|
+
compressedTokens,
|
|
125
|
+
compressionRatio: originalTokens > 0 ? compressedTokens / originalTokens : 1,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Compress messages in-place (mutates array)
|
|
131
|
+
*/
|
|
132
|
+
compressInPlace(messages: Message[]): void {
|
|
133
|
+
for (let i = 0; i < messages.length; i++) {
|
|
134
|
+
messages[i] = this.compressMessage(messages[i]);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Get compression statistics
|
|
140
|
+
*/
|
|
141
|
+
getStats(result: CompressionResult): {
|
|
142
|
+
tokensReduced: number;
|
|
143
|
+
percentReduction: string;
|
|
144
|
+
} {
|
|
145
|
+
const tokensReduced = result.originalTokens - result.compressedTokens;
|
|
146
|
+
const percentReduction = result.originalTokens > 0
|
|
147
|
+
? ((tokensReduced / result.originalTokens) * 100).toFixed(1) + "%"
|
|
148
|
+
: "0%";
|
|
149
|
+
|
|
150
|
+
return {
|
|
151
|
+
tokensReduced,
|
|
152
|
+
percentReduction,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export default InterAgentCompressor;
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Schema-Contracted Prompts - Token Optimization Pattern #4
|
|
3
|
+
*
|
|
4
|
+
* Use structured schemas to reduce prompt size.
|
|
5
|
+
* Instead of verbose descriptions, reference schema directly.
|
|
6
|
+
*
|
|
7
|
+
* Based on: arXiv:2608.17188 - Token Optimization and Context Window Management
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export interface SchemaContractConfig {
|
|
11
|
+
includeExamples: boolean;
|
|
12
|
+
maxSchemaSize: number;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface SchemaInfo {
|
|
16
|
+
name: string;
|
|
17
|
+
description?: string;
|
|
18
|
+
schema: Record<string, unknown>;
|
|
19
|
+
examples?: Array<Record<string, unknown>>;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Generate schema instruction for prompt
|
|
24
|
+
*/
|
|
25
|
+
function generateSchemaInstruction(schema: SchemaInfo): string {
|
|
26
|
+
const parts: string[] = [];
|
|
27
|
+
|
|
28
|
+
parts.push(`Response Schema: ${schema.name}`);
|
|
29
|
+
|
|
30
|
+
if (schema.description) {
|
|
31
|
+
parts.push(`Description: ${schema.description}`);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
parts.push(`Schema: ${JSON.stringify(schema.schema)}`);
|
|
35
|
+
|
|
36
|
+
if (schema.examples && schema.examples.length > 0) {
|
|
37
|
+
parts.push(`Examples: ${JSON.stringify(schema.examples.slice(0, 2))}`);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return parts.join("\n");
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export class SchemaContractor {
|
|
44
|
+
private config: SchemaContractConfig;
|
|
45
|
+
|
|
46
|
+
constructor(config: Partial<SchemaContractConfig> = {}) {
|
|
47
|
+
this.config = {
|
|
48
|
+
includeExamples: config.includeExamples ?? true,
|
|
49
|
+
maxSchemaSize: config.maxSchemaSize ?? 500,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Contract a prompt by injecting schema reference
|
|
55
|
+
*/
|
|
56
|
+
contract(prompt: string, schema: SchemaInfo): string {
|
|
57
|
+
const schemaInstruction = generateSchemaInstruction(schema);
|
|
58
|
+
|
|
59
|
+
return `${prompt}
|
|
60
|
+
|
|
61
|
+
IMPORTANT: Your response MUST conform to this schema:
|
|
62
|
+
${schemaInstruction}
|
|
63
|
+
|
|
64
|
+
Respond ONLY with valid JSON matching the above schema.`;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Contract with inline schema (smaller)
|
|
69
|
+
*/
|
|
70
|
+
contractInline(prompt: string, schema: SchemaInfo): string {
|
|
71
|
+
const schemaJson = JSON.stringify(schema.schema).substring(0, this.config.maxSchemaSize);
|
|
72
|
+
|
|
73
|
+
return `${prompt}
|
|
74
|
+
|
|
75
|
+
Response Schema: ${schema.name}
|
|
76
|
+
${schemaJson}`;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Extract schema from Pydantic-style model definition
|
|
81
|
+
*/
|
|
82
|
+
static extractSchemaFromModel(modelDef: string): SchemaInfo {
|
|
83
|
+
// Simple parser for TypeScript/JavaScript interface or Python Pydantic
|
|
84
|
+
const nameMatch = modelDef.match(/class\s+(\w+)/);
|
|
85
|
+
const name = nameMatch ? nameMatch[1] : "Response";
|
|
86
|
+
|
|
87
|
+
// Extract field types (simplified)
|
|
88
|
+
const fields: Record<string, string> = {};
|
|
89
|
+
const fieldMatches = modelDef.matchAll(/(\w+)(\??):\s*(\w+)/g);
|
|
90
|
+
for (const match of fieldMatches) {
|
|
91
|
+
fields[match[1]] = match[3];
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return {
|
|
95
|
+
name,
|
|
96
|
+
schema: fields as Record<string, unknown>,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export default SchemaContractor;
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Semantic Cache - Token Optimization Pattern #1
|
|
3
|
+
*
|
|
4
|
+
* Cache semantically similar queries instead of exact-match caching.
|
|
5
|
+
* Uses embedding similarity (cosine) to find cached responses.
|
|
6
|
+
*
|
|
7
|
+
* Based on: arXiv:2608.17188 - Token Optimization and Context Window Management
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export interface SemanticCacheConfig {
|
|
11
|
+
enabled: boolean;
|
|
12
|
+
similarityThreshold: number; // 0.85 default
|
|
13
|
+
maxEntries: number;
|
|
14
|
+
embeddingModel: string; // e.g., "sentence-transformers/all-MiniLM-L6-v2"
|
|
15
|
+
ttlSeconds: number;
|
|
16
|
+
cacheDir?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface SemanticCacheEntry {
|
|
20
|
+
query: string;
|
|
21
|
+
embedding: number[];
|
|
22
|
+
response: string;
|
|
23
|
+
model: string;
|
|
24
|
+
tokensSaved: number;
|
|
25
|
+
costSaved: number;
|
|
26
|
+
cachedAt: number;
|
|
27
|
+
expiresAt: number;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Compute cosine similarity between two embeddings
|
|
32
|
+
*/
|
|
33
|
+
function cosineSimilarity(a: number[], b: number[]): number {
|
|
34
|
+
if (a.length !== b.length) return 0;
|
|
35
|
+
|
|
36
|
+
let dotProduct = 0;
|
|
37
|
+
let normA = 0;
|
|
38
|
+
let normB = 0;
|
|
39
|
+
|
|
40
|
+
for (let i = 0; i < a.length; i++) {
|
|
41
|
+
dotProduct += a[i] * b[i];
|
|
42
|
+
normA += a[i] * a[i];
|
|
43
|
+
normB += b[i] * b[i];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const denominator = Math.sqrt(normA) * Math.sqrt(normB);
|
|
47
|
+
return denominator === 0 ? 0 : dotProduct / denominator;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Simple embedding generator using TF-IDF style vectors
|
|
52
|
+
* For production, replace with actual sentence transformer embeddings
|
|
53
|
+
*/
|
|
54
|
+
function simpleHash(str: string): number {
|
|
55
|
+
let hash = 0;
|
|
56
|
+
for (let i = 0; i < str.length; i++) {
|
|
57
|
+
hash = ((hash << 5) - hash) + str.charCodeAt(i);
|
|
58
|
+
hash = hash & hash;
|
|
59
|
+
}
|
|
60
|
+
return Math.abs(hash);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function generateEmbedding(text: string, dimensions = 384): number[] {
|
|
64
|
+
const words = text.toLowerCase().split(/\s+/);
|
|
65
|
+
const embedding: number[] = new Array(dimensions).fill(0);
|
|
66
|
+
|
|
67
|
+
// Simple hash-based embedding for demo
|
|
68
|
+
// In production, use: sentence-transformers or OpenAI embeddings
|
|
69
|
+
words.forEach((word, idx) => {
|
|
70
|
+
const bucket = simpleHash(word) % dimensions;
|
|
71
|
+
embedding[bucket] += 1 / (idx + 1); // Position weighting
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
// Normalize
|
|
75
|
+
const norm = Math.sqrt(embedding.reduce((sum, val) => sum + val * val, 0));
|
|
76
|
+
return norm > 0 ? embedding.map(v => v / norm) : embedding;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export class SemanticCache {
|
|
80
|
+
private cache: Map<string, SemanticCacheEntry> = new Map();
|
|
81
|
+
private config: SemanticCacheConfig;
|
|
82
|
+
private hits = 0;
|
|
83
|
+
private misses = 0;
|
|
84
|
+
private embeddingCache: Map<string, number[]> = new Map();
|
|
85
|
+
|
|
86
|
+
constructor(config: Partial<SemanticCacheConfig> = {}) {
|
|
87
|
+
this.config = {
|
|
88
|
+
enabled: config.enabled ?? true,
|
|
89
|
+
similarityThreshold: config.similarityThreshold ?? 0.85,
|
|
90
|
+
maxEntries: config.maxEntries ?? 1000,
|
|
91
|
+
embeddingModel: config.embeddingModel ?? "local-tfidf",
|
|
92
|
+
ttlSeconds: config.ttlSeconds ?? 3600,
|
|
93
|
+
cacheDir: config.cacheDir,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Get embedding for text (with caching)
|
|
99
|
+
*/
|
|
100
|
+
private async getEmbedding(text: string): Promise<number[]> {
|
|
101
|
+
// Check cache first
|
|
102
|
+
const cached = this.embeddingCache.get(text);
|
|
103
|
+
if (cached) return cached;
|
|
104
|
+
|
|
105
|
+
// Generate embedding
|
|
106
|
+
const embedding = generateEmbedding(text);
|
|
107
|
+
this.embeddingCache.set(text, embedding);
|
|
108
|
+
|
|
109
|
+
// Limit cache size
|
|
110
|
+
if (this.embeddingCache.size > this.config.maxEntries * 2) {
|
|
111
|
+
const keys = Array.from(this.embeddingCache.keys());
|
|
112
|
+
keys.slice(0, keys.length - this.config.maxEntries).forEach(k => {
|
|
113
|
+
this.embeddingCache.delete(k);
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return embedding;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Generate cache key from query
|
|
122
|
+
*/
|
|
123
|
+
private generateKey(query: string): string {
|
|
124
|
+
// Simple hash function instead of crypto
|
|
125
|
+
const hash = simpleHash(query.toLowerCase().trim());
|
|
126
|
+
return hash.toString(16).padStart(8, '0') + simpleHash(query).toString(16).padStart(8, '0');
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Find best matching cached entry using cosine similarity
|
|
131
|
+
*/
|
|
132
|
+
async get(query: string): Promise<{ response: string; similarity: number; tokensSaved: number; costSaved: number } | null> {
|
|
133
|
+
if (!this.config.enabled) return null;
|
|
134
|
+
|
|
135
|
+
const queryEmbedding = await this.getEmbedding(query);
|
|
136
|
+
let bestMatch: { key: string; similarity: number } | null = null;
|
|
137
|
+
|
|
138
|
+
// Search for most similar cached entry
|
|
139
|
+
for (const [key, entry] of this.cache.entries()) {
|
|
140
|
+
// Check expiration
|
|
141
|
+
if (Date.now() > entry.expiresAt) {
|
|
142
|
+
this.cache.delete(key);
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const similarity = cosineSimilarity(queryEmbedding, entry.embedding);
|
|
147
|
+
if (similarity >= this.config.similarityThreshold) {
|
|
148
|
+
if (!bestMatch || similarity > bestMatch.similarity) {
|
|
149
|
+
bestMatch = { key, similarity };
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (!bestMatch) {
|
|
155
|
+
this.misses++;
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const entry = this.cache.get(bestMatch.key)!;
|
|
160
|
+
this.hits++;
|
|
161
|
+
|
|
162
|
+
return {
|
|
163
|
+
response: entry.response,
|
|
164
|
+
similarity: bestMatch.similarity,
|
|
165
|
+
tokensSaved: entry.tokensSaved,
|
|
166
|
+
costSaved: entry.costSaved,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Store query-response pair in semantic cache
|
|
172
|
+
*/
|
|
173
|
+
async set(
|
|
174
|
+
query: string,
|
|
175
|
+
response: string,
|
|
176
|
+
options: {
|
|
177
|
+
model?: string;
|
|
178
|
+
tokensSaved?: number;
|
|
179
|
+
costSaved?: number;
|
|
180
|
+
} = {}
|
|
181
|
+
): Promise<void> {
|
|
182
|
+
if (!this.config.enabled) return;
|
|
183
|
+
|
|
184
|
+
const embedding = await this.getEmbedding(query);
|
|
185
|
+
const now = Date.now();
|
|
186
|
+
const key = this.generateKey(query);
|
|
187
|
+
|
|
188
|
+
// Evict oldest if at capacity
|
|
189
|
+
if (this.cache.size >= this.config.maxEntries) {
|
|
190
|
+
this.evictOldest();
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
this.cache.set(key, {
|
|
194
|
+
query,
|
|
195
|
+
embedding,
|
|
196
|
+
response,
|
|
197
|
+
model: options.model ?? "unknown",
|
|
198
|
+
tokensSaved: options.tokensSaved ?? 0,
|
|
199
|
+
costSaved: options.costSaved ?? 0,
|
|
200
|
+
cachedAt: now,
|
|
201
|
+
expiresAt: now + this.config.ttlSeconds * 1000,
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Evict oldest entry
|
|
207
|
+
*/
|
|
208
|
+
private evictOldest(): void {
|
|
209
|
+
let oldestKey: string | null = null;
|
|
210
|
+
let oldestTime = Infinity;
|
|
211
|
+
|
|
212
|
+
for (const [key, entry] of this.cache.entries()) {
|
|
213
|
+
if (entry.cachedAt < oldestTime) {
|
|
214
|
+
oldestTime = entry.cachedAt;
|
|
215
|
+
oldestKey = key;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (oldestKey) {
|
|
220
|
+
this.cache.delete(oldestKey);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Get cache statistics
|
|
226
|
+
*/
|
|
227
|
+
getStats(): { hits: number; misses: number; size: number; hitRate: number } {
|
|
228
|
+
const total = this.hits + this.misses;
|
|
229
|
+
return {
|
|
230
|
+
hits: this.hits,
|
|
231
|
+
misses: this.misses,
|
|
232
|
+
size: this.cache.size,
|
|
233
|
+
hitRate: total > 0 ? this.hits / total : 0,
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Clear cache
|
|
239
|
+
*/
|
|
240
|
+
clear(): void {
|
|
241
|
+
this.cache.clear();
|
|
242
|
+
this.embeddingCache.clear();
|
|
243
|
+
this.hits = 0;
|
|
244
|
+
this.misses = 0;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
export default SemanticCache;
|