adaptive-memory-multi-model-router 2.15.3 → 2.16.0
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/CHANGELOG.md +7 -1
- package/README_ja.md +2 -2
- package/README_zh.md +1 -1
- package/apps/cost-calculator/README.md +72 -0
- package/apps/cost-calculator/calculator.css +280 -0
- package/apps/cost-calculator/calculator.js +150 -0
- package/apps/cost-calculator/index.html +321 -0
- package/apps/cost-calculator/package.json +13 -0
- package/articles/ANNOUNCEMENT_reddit_ml.md +76 -0
- package/articles/ANNOUNCEMENT_vc/347/244/276/345/214/272.md +71 -0
- package/articles/ANNOUNCEMENT_vercel.md +85 -0
- package/dist/providers/providerConfig.d.ts +5 -1
- package/dist/providers/providerConfig.js +1006 -1
- package/dist/providers/providerConfig.js.map +1 -1
- package/docs/llms.txt +3 -3
- package/package.json +2 -2
- package/packages/a3m-vercel-ai/README.md +161 -0
- package/packages/a3m-vercel-ai/dist/a3m-language-model.d.ts +12 -0
- package/packages/a3m-vercel-ai/dist/a3m-language-model.d.ts.map +1 -0
- package/packages/a3m-vercel-ai/dist/a3m-language-model.js +289 -0
- package/packages/a3m-vercel-ai/dist/a3m-language-model.js.map +1 -0
- package/packages/a3m-vercel-ai/dist/index.d.ts +82 -0
- package/packages/a3m-vercel-ai/dist/index.d.ts.map +1 -0
- package/packages/a3m-vercel-ai/dist/index.js +79 -0
- package/packages/a3m-vercel-ai/dist/index.js.map +1 -0
- package/packages/a3m-vercel-ai/dist/types.d.ts +97 -0
- package/packages/a3m-vercel-ai/dist/types.d.ts.map +1 -0
- package/packages/a3m-vercel-ai/dist/types.js +5 -0
- package/packages/a3m-vercel-ai/dist/types.js.map +1 -0
- package/packages/a3m-vercel-ai/package-lock.json +969 -0
- package/packages/a3m-vercel-ai/package.json +46 -0
- package/packages/a3m-vercel-ai/src/a3m-language-model.ts +381 -0
- package/packages/a3m-vercel-ai/src/index.ts +104 -0
- package/packages/a3m-vercel-ai/src/types.ts +116 -0
- package/packages/a3m-vercel-ai/tsconfig.json +20 -0
- package/src/providers/providerConfig.ts +1053 -1
- package/summary.txt +38 -0
package/CHANGELOG.md
CHANGED
|
@@ -14,7 +14,13 @@
|
|
|
14
14
|
### Changed
|
|
15
15
|
- Cleanup removed 4 unused deps, fixed 0 vulns, deduplicated
|
|
16
16
|
|
|
17
|
-
## [2.
|
|
17
|
+
## [2.16.0] — 2026-08-12
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
- **80+ providers** (up from 36): Cloudflare Workers AI, HuggingFace Inference, Modal, Lepton AI, NVIDIA NIM, Baseten, RunPod, Predibase, Featherless AI, Abacus AI, Hyperbolic, Stability AI, SiliconFlow, Yandex GPT, Tencent Hunyuan, VolcEngine (Doubao), Baidu Qianfan, Kuaishou, 360 AI, Naver Clova, Kakao Brain, Upstage, SkyFire, LIVEPEOPLE, Scale AI, Titanium Network, Aizel Cloud, Poe, Phind, Banana, Forefront AI, You.com, Komo, Shine AI, AIDE Labs, Writer API, Databricks, La Plateforme, and more
|
|
21
|
+
|
|
22
|
+
### Changed
|
|
23
|
+
- Provider expansion: 36 → 80+ providers across free, cheap, mid, premium, and enterprise tiers — 2026-05-28
|
|
18
24
|
|
|
19
25
|
### Added
|
|
20
26
|
- MCP server for AI agent integration
|
package/README_ja.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# A3M Router 🔀 — 成本最优的LLMRouter & 并行执行
|
|
2
2
|
|
|
3
|
-
**💰 $0.0768/1Kリクエスト ·
|
|
3
|
+
**💰 $0.0768/1Kリクエスト · 80+プロバイダー · メモリ付きルーティング**
|
|
4
4
|
|
|
5
5
|
[English](./README.md) | [中文](./README_zh.md) | [日本語](./README_ja.md)
|
|
6
6
|
|
|
@@ -40,7 +40,7 @@ A3M: モデルA ║ モデルB ║ モデルC → スコアリングで最良
|
|
|
40
40
|
- 🔄 **セマンティックキャッシュ** — 30%+ヒット率コスト節約
|
|
41
41
|
- 🛡️ **予算強制** — クエリごとコスト追跡,超過防止
|
|
42
42
|
- ⚡ **高速起動** — <100ms、19.5KB、ML依存なし
|
|
43
|
-
- 🌐 **
|
|
43
|
+
- 🌐 **80+プロバイダー** — OpenAI, Anthropic, DeepSeek, Groq, NVIDIA等
|
|
44
44
|
|
|
45
45
|
## クイックスタート
|
|
46
46
|
|
package/README_zh.md
CHANGED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# A3M Router Cost Calculator
|
|
2
|
+
|
|
3
|
+
**Live:** https://a3m.router/calculator
|
|
4
|
+
|
|
5
|
+
Drop-in interactive calculator. No build step. No npm dependencies for the calculator itself.
|
|
6
|
+
|
|
7
|
+
## What it does
|
|
8
|
+
|
|
9
|
+
- Input: monthly request volume + model mix
|
|
10
|
+
- Output: side-by-side cost comparison (baseline vs A3M routed)
|
|
11
|
+
- Generates shareable URL with encoded config (base64url)
|
|
12
|
+
- Exports comparison as CSV
|
|
13
|
+
|
|
14
|
+
## Quick Start
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
# Open directly
|
|
18
|
+
open apps/cost-calculator/index.html
|
|
19
|
+
|
|
20
|
+
# Or serve locally
|
|
21
|
+
npx serve apps/cost-calculator
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Embed in any site
|
|
25
|
+
|
|
26
|
+
```html
|
|
27
|
+
<iframe
|
|
28
|
+
src="https://a3m.router/calculator"
|
|
29
|
+
width="100%"
|
|
30
|
+
height="700"
|
|
31
|
+
frameborder="0"
|
|
32
|
+
style="border-radius: 12px;"
|
|
33
|
+
></iframe>
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Architecture
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
calculator.js — Pure JS, no dependencies. All pricing, routing, and encoding logic.
|
|
40
|
+
calculator.css — Standalone styles. Dark theme, responsive.
|
|
41
|
+
index.html — Self-contained. Vanilla JS. Loads calculator.js.
|
|
42
|
+
package.json — vitest for unit testing the pure JS logic.
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Testing
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
cd apps/cost-calculator
|
|
49
|
+
npm install
|
|
50
|
+
npm test
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Models Supported
|
|
54
|
+
|
|
55
|
+
| Premium Model | Routed To | Savings |
|
|
56
|
+
|---------------|-----------|---------|
|
|
57
|
+
| GPT-4o | GPT-4o-mini | ~35% |
|
|
58
|
+
| Claude 3.5 Sonnet | Claude 3 Haiku | ~40% |
|
|
59
|
+
| Gemini 1.5 Pro | Gemini 1.5 Flash | ~45% |
|
|
60
|
+
| Groq Llama 3.3 70B | Groq Mixtral 8x7B | ~30% |
|
|
61
|
+
|
|
62
|
+
## Pricing Source
|
|
63
|
+
|
|
64
|
+
OpenRouter provider rates (per 1M tokens). Update `PRICING` constant in `calculator.js` to reflect latest rates.
|
|
65
|
+
|
|
66
|
+
## Next Steps
|
|
67
|
+
|
|
68
|
+
- [ ] Deploy to `a3m.router/calculator` (Vercel static)
|
|
69
|
+
- [ ] Add UTM tracking on share links
|
|
70
|
+
- [ ] Add "embed widget" script for dynamic resize
|
|
71
|
+
- [ ] Write HN + Reddit post with actual example calculations
|
|
72
|
+
- [ ] Add unit tests with vitest
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
/* A3M Router Cost Calculator Styles */
|
|
2
|
+
|
|
3
|
+
:root {
|
|
4
|
+
--a3m-primary: #6366f1;
|
|
5
|
+
--a3m-accent: #818cf8;
|
|
6
|
+
--a3m-bg: #0f0f1a;
|
|
7
|
+
--a3m-surface: #1a1a2e;
|
|
8
|
+
--a3m-border: #2d2d4a;
|
|
9
|
+
--a3m-text: #e2e8f0;
|
|
10
|
+
--a3m-muted: #94a3b8;
|
|
11
|
+
--a3m-green: #22c55e;
|
|
12
|
+
--a3m-red: #ef4444;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
16
|
+
|
|
17
|
+
body {
|
|
18
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
19
|
+
background: var(--a3m-bg);
|
|
20
|
+
color: var(--a3m-text);
|
|
21
|
+
min-height: 100vh;
|
|
22
|
+
padding: 2rem 1rem;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.container {
|
|
26
|
+
max-width: 900px;
|
|
27
|
+
margin: 0 auto;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
h1 {
|
|
31
|
+
font-size: 1.75rem;
|
|
32
|
+
font-weight: 700;
|
|
33
|
+
text-align: center;
|
|
34
|
+
margin-bottom: 0.5rem;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.subtitle {
|
|
38
|
+
text-align: center;
|
|
39
|
+
color: var(--a3m-muted);
|
|
40
|
+
margin-bottom: 2rem;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.card {
|
|
44
|
+
background: var(--a3m-surface);
|
|
45
|
+
border: 1px solid var(--a3m-border);
|
|
46
|
+
border-radius: 12px;
|
|
47
|
+
padding: 1.5rem;
|
|
48
|
+
margin-bottom: 1.5rem;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.card-title {
|
|
52
|
+
font-size: 0.875rem;
|
|
53
|
+
font-weight: 600;
|
|
54
|
+
text-transform: uppercase;
|
|
55
|
+
letter-spacing: 0.05em;
|
|
56
|
+
color: var(--a3m-muted);
|
|
57
|
+
margin-bottom: 1rem;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/* Input grid */
|
|
61
|
+
.input-grid {
|
|
62
|
+
display: grid;
|
|
63
|
+
grid-template-columns: 1fr 1fr;
|
|
64
|
+
gap: 1rem;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
@media (max-width: 600px) {
|
|
68
|
+
.input-grid { grid-template-columns: 1fr; }
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.input-group {
|
|
72
|
+
display: flex;
|
|
73
|
+
flex-direction: column;
|
|
74
|
+
gap: 0.5rem;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.input-group label {
|
|
78
|
+
font-size: 0.875rem;
|
|
79
|
+
font-weight: 500;
|
|
80
|
+
color: var(--a3m-muted);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.input-group input,
|
|
84
|
+
.input-group select {
|
|
85
|
+
padding: 0.625rem 0.875rem;
|
|
86
|
+
background: var(--a3m-bg);
|
|
87
|
+
border: 1px solid var(--a3m-border);
|
|
88
|
+
border-radius: 8px;
|
|
89
|
+
color: var(--a3m-text);
|
|
90
|
+
font-size: 1rem;
|
|
91
|
+
width: 100%;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.input-group input:focus,
|
|
95
|
+
.input-group select:focus {
|
|
96
|
+
outline: none;
|
|
97
|
+
border-color: var(--a3m-primary);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/* Model mix rows */
|
|
101
|
+
.model-mix {
|
|
102
|
+
display: flex;
|
|
103
|
+
flex-direction: column;
|
|
104
|
+
gap: 0.75rem;
|
|
105
|
+
margin-top: 1rem;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
.model-row {
|
|
109
|
+
display: grid;
|
|
110
|
+
grid-template-columns: 1fr 100px auto;
|
|
111
|
+
gap: 0.5rem;
|
|
112
|
+
align-items: center;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.model-row select,
|
|
116
|
+
.model-row input {
|
|
117
|
+
padding: 0.5rem;
|
|
118
|
+
background: var(--a3m-bg);
|
|
119
|
+
border: 1px solid var(--a3m-border);
|
|
120
|
+
border-radius: 6px;
|
|
121
|
+
color: var(--a3m-text);
|
|
122
|
+
font-size: 0.875rem;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
.model-row span {
|
|
126
|
+
color: var(--a3m-muted);
|
|
127
|
+
font-size: 0.875rem;
|
|
128
|
+
white-space: nowrap;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
.btn {
|
|
132
|
+
display: inline-flex;
|
|
133
|
+
align-items: center;
|
|
134
|
+
gap: 0.5rem;
|
|
135
|
+
padding: 0.625rem 1.25rem;
|
|
136
|
+
background: var(--a3m-primary);
|
|
137
|
+
color: white;
|
|
138
|
+
border: none;
|
|
139
|
+
border-radius: 8px;
|
|
140
|
+
font-size: 0.875rem;
|
|
141
|
+
font-weight: 600;
|
|
142
|
+
cursor: pointer;
|
|
143
|
+
transition: background 0.2s;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
.btn:hover { background: var(--a3m-accent); }
|
|
147
|
+
.btn-secondary {
|
|
148
|
+
background: var(--a3m-border);
|
|
149
|
+
color: var(--a3m-text);
|
|
150
|
+
}
|
|
151
|
+
.btn-secondary:hover { background: #3d3d5a; }
|
|
152
|
+
|
|
153
|
+
.btn-row {
|
|
154
|
+
display: flex;
|
|
155
|
+
gap: 0.75rem;
|
|
156
|
+
margin-top: 1.25rem;
|
|
157
|
+
flex-wrap: wrap;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/* Results */
|
|
161
|
+
.results-grid {
|
|
162
|
+
display: grid;
|
|
163
|
+
grid-template-columns: repeat(3, 1fr);
|
|
164
|
+
gap: 1rem;
|
|
165
|
+
margin-bottom: 1.5rem;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
@media (max-width: 600px) {
|
|
169
|
+
.results-grid { grid-template-columns: 1fr; }
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
.result-card {
|
|
173
|
+
background: var(--a3m-bg);
|
|
174
|
+
border: 1px solid var(--a3m-border);
|
|
175
|
+
border-radius: 10px;
|
|
176
|
+
padding: 1.25rem;
|
|
177
|
+
text-align: center;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
.result-label {
|
|
181
|
+
font-size: 0.75rem;
|
|
182
|
+
font-weight: 600;
|
|
183
|
+
text-transform: uppercase;
|
|
184
|
+
letter-spacing: 0.05em;
|
|
185
|
+
color: var(--a3m-muted);
|
|
186
|
+
margin-bottom: 0.5rem;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
.result-value {
|
|
190
|
+
font-size: 1.75rem;
|
|
191
|
+
font-weight: 700;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
.result-value.savings { color: var(--a3m-green); }
|
|
195
|
+
.result-value.baseline { color: var(--a3m-muted); text-decoration: line-through; }
|
|
196
|
+
|
|
197
|
+
/* Breakdown table */
|
|
198
|
+
.breakdown-table {
|
|
199
|
+
width: 100%;
|
|
200
|
+
border-collapse: collapse;
|
|
201
|
+
font-size: 0.875rem;
|
|
202
|
+
margin-top: 1rem;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
.breakdown-table th {
|
|
206
|
+
text-align: left;
|
|
207
|
+
padding: 0.625rem 0.75rem;
|
|
208
|
+
color: var(--a3m-muted);
|
|
209
|
+
font-weight: 600;
|
|
210
|
+
font-size: 0.75rem;
|
|
211
|
+
text-transform: uppercase;
|
|
212
|
+
border-bottom: 1px solid var(--a3m-border);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
.breakdown-table td {
|
|
216
|
+
padding: 0.625rem 0.75rem;
|
|
217
|
+
border-bottom: 1px solid var(--a3m-border);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
.breakdown-table tr:last-child td { border-bottom: none; }
|
|
221
|
+
.breakdown-table .num { text-align: right; font-variant-numeric: tabular-nums; }
|
|
222
|
+
.breakdown-table .savings-cell { color: var(--a3m-green); }
|
|
223
|
+
|
|
224
|
+
/* Share bar */
|
|
225
|
+
.share-bar {
|
|
226
|
+
display: flex;
|
|
227
|
+
gap: 0.5rem;
|
|
228
|
+
align-items: center;
|
|
229
|
+
margin-top: 1rem;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
.share-bar input {
|
|
233
|
+
flex: 1;
|
|
234
|
+
padding: 0.5rem 0.75rem;
|
|
235
|
+
background: var(--a3m-bg);
|
|
236
|
+
border: 1px solid var(--a3m-border);
|
|
237
|
+
border-radius: 6px;
|
|
238
|
+
color: var(--a3m-text);
|
|
239
|
+
font-size: 0.8rem;
|
|
240
|
+
font-family: monospace;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/* Chart */
|
|
244
|
+
.chart-container {
|
|
245
|
+
position: relative;
|
|
246
|
+
height: 250px;
|
|
247
|
+
margin: 1.5rem 0;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
.chart-bar {
|
|
251
|
+
position: absolute;
|
|
252
|
+
bottom: 0;
|
|
253
|
+
width: 40%;
|
|
254
|
+
border-radius: 6px 6px 0 0;
|
|
255
|
+
transition: height 0.5s ease;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
.chart-bar.baseline {
|
|
259
|
+
left: 15%;
|
|
260
|
+
background: var(--a3m-muted);
|
|
261
|
+
opacity: 0.5;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
.chart-bar.a3m {
|
|
265
|
+
right: 15%;
|
|
266
|
+
background: var(--a3m-primary);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/* Hidden until calculated */
|
|
270
|
+
.hidden { display: none; }
|
|
271
|
+
|
|
272
|
+
/* Footer */
|
|
273
|
+
.footer {
|
|
274
|
+
text-align: center;
|
|
275
|
+
color: var(--a3m-muted);
|
|
276
|
+
font-size: 0.75rem;
|
|
277
|
+
margin-top: 2rem;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
.footer a { color: var(--a3m-accent); text-decoration: none; }
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A3M Router Cost Calculator
|
|
3
|
+
* Pure JS, no dependencies.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// --- Pricing constants (per 1M tokens) ---
|
|
7
|
+
const PRICING = {
|
|
8
|
+
'gpt-4o': { input: 5.0, output: 15.0 },
|
|
9
|
+
'gpt-4o-mini': { input: 0.15, output: 0.60 },
|
|
10
|
+
'claude-3.5-sonnet': { input: 3.0, output: 15.0 },
|
|
11
|
+
'claude-3-haiku': { input: 0.25, output: 1.25 },
|
|
12
|
+
'gemini-1.5-pro': { input: 3.5, output: 10.5 },
|
|
13
|
+
'gemini-1.5-flash': { input: 0.075, output: 0.30 },
|
|
14
|
+
'groq-llama-3.3-70b': { input: 0.59, output: 0.79 },
|
|
15
|
+
'groq-mixtral-8x7b': { input: 0.24, output: 0.24 },
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const ROUTING_SAVINGS = {
|
|
19
|
+
// % of requests A3M routes to cheaper model
|
|
20
|
+
'gpt-4o': 0.35, // 35% routed to 4o-mini
|
|
21
|
+
'claude-3.5-sonnet': 0.40, // 40% routed to Haiku
|
|
22
|
+
'gemini-1.5-pro': 0.45, // 45% routed to Flash
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const AVG_TOKENS = {
|
|
26
|
+
input: 500, // avg input tokens
|
|
27
|
+
output: 800, // avg output tokens
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* @param {{ requestsPerMonth: number, modelMix: Array<{model: string, percent: number}> }} cfg
|
|
32
|
+
* @returns {{ totalCost: number, breakdown: Array<{model, requests, cost}> }}
|
|
33
|
+
*/
|
|
34
|
+
function calculateBaselineCost({ requestsPerMonth, modelMix }) {
|
|
35
|
+
const breakdown = modelMix.map(({ model, percent }) => {
|
|
36
|
+
const reqCount = Math.round(requestsPerMonth * (percent / 100));
|
|
37
|
+
const pricing = PRICING[model] || { input: 3.0, output: 15.0 };
|
|
38
|
+
const cost = reqCount * (
|
|
39
|
+
(pricing.input * AVG_TOKENS.input / 1_000_000) +
|
|
40
|
+
(pricing.output * AVG_TOKENS.output / 1_000_000)
|
|
41
|
+
);
|
|
42
|
+
return { model, requests: reqCount, cost };
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
const totalCost = breakdown.reduce((sum, b) => sum + b.cost, 0);
|
|
46
|
+
return { totalCost, breakdown };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* @param {{ requestsPerMonth: number, modelMix: Array<{model: string, percent: number}> }} cfg
|
|
51
|
+
* @returns {{ totalCost: number, breakdown: Array<{model, requests, cost, routedTo?, savings?}> }}
|
|
52
|
+
*/
|
|
53
|
+
function calculateA3mCost({ requestsPerMonth, modelMix }) {
|
|
54
|
+
const breakdown = modelMix.map(({ model, percent }) => {
|
|
55
|
+
const reqCount = Math.round(requestsPerMonth * (percent / 100));
|
|
56
|
+
const routingSavings = ROUTING_SAVINGS[model] || 0;
|
|
57
|
+
const routedRequests = Math.round(reqCount * routingSavings);
|
|
58
|
+
|
|
59
|
+
// Baseline: all requests on premium model
|
|
60
|
+
const pricing = PRICING[model] || { input: 3.0, output: 15.0 };
|
|
61
|
+
const baselineCost = reqCount * (
|
|
62
|
+
(pricing.input * AVG_TOKENS.input / 1_000_000) +
|
|
63
|
+
(pricing.output * AVG_TOKENS.output / 1_000_000)
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
// A3M: some routed to cheaper model
|
|
67
|
+
// Find a cheaper alternative (heuristic: -mini, -flash, -haiku, or groq)
|
|
68
|
+
const cheaper = findCheaperModel(model);
|
|
69
|
+
const cheaperPricing = cheaper ? PRICING[cheaper] : pricing;
|
|
70
|
+
|
|
71
|
+
const a3mCost = (reqCount - routedRequests) * (
|
|
72
|
+
(pricing.input * AVG_TOKENS.input / 1_000_000) +
|
|
73
|
+
(pricing.output * AVG_TOKENS.output / 1_000_000)
|
|
74
|
+
) + routedRequests * (
|
|
75
|
+
(cheaperPricing.input * AVG_TOKENS.input / 1_000_000) +
|
|
76
|
+
(cheaperPricing.output * AVG_TOKENS.output / 1_000_000)
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
return {
|
|
80
|
+
model,
|
|
81
|
+
requests: reqCount,
|
|
82
|
+
cost: a3mCost,
|
|
83
|
+
routedTo: routedRequests > 0 ? cheaper : null,
|
|
84
|
+
savings: baselineCost - a3mCost,
|
|
85
|
+
};
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
const totalCost = breakdown.reduce((sum, b) => sum + b.cost, 0);
|
|
89
|
+
const totalSavings = breakdown.reduce((sum, b) => sum + (b.savings || 0), 0);
|
|
90
|
+
return { totalCost, breakdown, totalSavings };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function findCheaperModel(model) {
|
|
94
|
+
const cheaperMap = {
|
|
95
|
+
'gpt-4o': 'gpt-4o-mini',
|
|
96
|
+
'claude-3.5-sonnet': 'claude-3-haiku',
|
|
97
|
+
'gemini-1.5-pro': 'gemini-1.5-flash',
|
|
98
|
+
'groq-llama-3.3-70b': 'groq-mixtral-8x7b',
|
|
99
|
+
};
|
|
100
|
+
return cheaperMap[model] || null;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* @param {number} baseline
|
|
105
|
+
* @param {number} a3m
|
|
106
|
+
* @returns {{ savingsPercent: number, savingsAmount: number }}
|
|
107
|
+
*/
|
|
108
|
+
function computeSavings(baseline, a3m) {
|
|
109
|
+
return {
|
|
110
|
+
savingsPercent: baseline > 0 ? ((baseline - a3m) / baseline) * 100 : 0,
|
|
111
|
+
savingsAmount: baseline - a3m,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Encodes calculator config into a URL-safe string
|
|
117
|
+
* @param {{ requestsPerMonth: number, modelMix: Array }} cfg
|
|
118
|
+
* @returns {string} base64url encoded config
|
|
119
|
+
*/
|
|
120
|
+
function encodeConfig({ requestsPerMonth, modelMix }) {
|
|
121
|
+
const json = JSON.stringify({ requestsPerMonth, modelMix });
|
|
122
|
+
// btoa but URL-safe
|
|
123
|
+
return btoa(json).replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Decodes a URL-safe base64 string back to config
|
|
128
|
+
* @param {string} encoded
|
|
129
|
+
* @returns {{ requestsPerMonth: number, modelMix: Array }}
|
|
130
|
+
*/
|
|
131
|
+
function decodeConfig(encoded) {
|
|
132
|
+
const base64 = encoded.replace(/-/g, '+').replace(/_/g, '/');
|
|
133
|
+
const json = atob(base64);
|
|
134
|
+
return JSON.parse(json);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Generates a full shareable URL
|
|
139
|
+
* @param {{ requestsPerMonth: number, modelMix: Array }} cfg
|
|
140
|
+
* @returns {string}
|
|
141
|
+
*/
|
|
142
|
+
function generateShareableLink(cfg) {
|
|
143
|
+
const encoded = encodeConfig(cfg);
|
|
144
|
+
return `${window.location.origin}${window.location.pathname}?cfg=${encoded}`;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// --- Exports for testing ---
|
|
148
|
+
if (typeof module !== 'undefined' && module.exports) {
|
|
149
|
+
module.exports = { calculateBaselineCost, calculateA3mCost, computeSavings, encodeConfig, decodeConfig, PRICING };
|
|
150
|
+
}
|