voyageai-cli 1.18.0 → 1.19.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/README.md +188 -235
- package/package.json +1 -1
- package/src/cli.js +2 -0
- package/src/commands/completions.js +26 -1
- package/src/commands/eval.js +300 -0
- package/src/commands/models.js +4 -4
- package/src/lib/catalog.js +1 -1
- package/src/lib/explanations.js +6 -6
- package/src/lib/metrics.js +174 -0
- package/src/playground/index.html +557 -34
|
@@ -629,16 +629,22 @@ select:focus { outline: none; border-color: var(--accent); }
|
|
|
629
629
|
.quant-rank-score { color: var(--text-muted); font-size: 11px; font-family: var(--mono); margin-top: 3px; }
|
|
630
630
|
|
|
631
631
|
/* Cost calculator */
|
|
632
|
+
.cost-controls {
|
|
633
|
+
display: grid;
|
|
634
|
+
grid-template-columns: 1fr 1fr;
|
|
635
|
+
gap: 16px 32px;
|
|
636
|
+
margin-bottom: 20px;
|
|
637
|
+
}
|
|
638
|
+
.cost-controls-full { grid-column: 1 / -1; }
|
|
632
639
|
.cost-slider-row {
|
|
633
640
|
display: flex;
|
|
634
641
|
align-items: center;
|
|
635
|
-
gap:
|
|
636
|
-
margin-bottom: 16px;
|
|
642
|
+
gap: 12px;
|
|
637
643
|
}
|
|
638
644
|
.cost-slider-label {
|
|
639
|
-
font-size:
|
|
645
|
+
font-size: 12px;
|
|
640
646
|
color: var(--text-dim);
|
|
641
|
-
min-width:
|
|
647
|
+
min-width: 110px;
|
|
642
648
|
}
|
|
643
649
|
.cost-slider {
|
|
644
650
|
flex: 1;
|
|
@@ -659,12 +665,155 @@ select:focus { outline: none; border-color: var(--accent); }
|
|
|
659
665
|
}
|
|
660
666
|
.cost-slider-value {
|
|
661
667
|
font-family: var(--mono);
|
|
662
|
-
font-size:
|
|
668
|
+
font-size: 13px;
|
|
663
669
|
color: var(--accent);
|
|
664
|
-
min-width:
|
|
670
|
+
min-width: 70px;
|
|
665
671
|
text-align: right;
|
|
666
672
|
font-weight: 600;
|
|
667
673
|
}
|
|
674
|
+
.cost-mode-toggle {
|
|
675
|
+
display: flex;
|
|
676
|
+
gap: 0;
|
|
677
|
+
border: 1px solid var(--border);
|
|
678
|
+
border-radius: 8px;
|
|
679
|
+
overflow: hidden;
|
|
680
|
+
width: fit-content;
|
|
681
|
+
}
|
|
682
|
+
.cost-mode-btn {
|
|
683
|
+
padding: 8px 20px;
|
|
684
|
+
background: transparent;
|
|
685
|
+
border: none;
|
|
686
|
+
color: var(--text-dim);
|
|
687
|
+
font-size: 13px;
|
|
688
|
+
cursor: pointer;
|
|
689
|
+
transition: all 0.2s;
|
|
690
|
+
font-family: var(--mono);
|
|
691
|
+
}
|
|
692
|
+
.cost-mode-btn.active {
|
|
693
|
+
background: var(--accent);
|
|
694
|
+
color: var(--bg);
|
|
695
|
+
font-weight: 600;
|
|
696
|
+
}
|
|
697
|
+
.cost-mode-btn:hover:not(.active) {
|
|
698
|
+
background: rgba(0, 212, 170, 0.1);
|
|
699
|
+
color: var(--text);
|
|
700
|
+
}
|
|
701
|
+
.cost-select {
|
|
702
|
+
background: var(--bg-input);
|
|
703
|
+
border: 1px solid var(--border);
|
|
704
|
+
border-radius: 6px;
|
|
705
|
+
color: var(--text);
|
|
706
|
+
font-family: var(--mono);
|
|
707
|
+
font-size: 12px;
|
|
708
|
+
padding: 6px 10px;
|
|
709
|
+
min-width: 160px;
|
|
710
|
+
}
|
|
711
|
+
.cost-select:focus { border-color: var(--accent); outline: none; }
|
|
712
|
+
.cost-model-row {
|
|
713
|
+
display: flex;
|
|
714
|
+
align-items: center;
|
|
715
|
+
gap: 12px;
|
|
716
|
+
margin-bottom: 12px;
|
|
717
|
+
}
|
|
718
|
+
.cost-model-label {
|
|
719
|
+
font-size: 12px;
|
|
720
|
+
color: var(--text-dim);
|
|
721
|
+
min-width: 110px;
|
|
722
|
+
}
|
|
723
|
+
.cost-summary {
|
|
724
|
+
display: grid;
|
|
725
|
+
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
|
726
|
+
gap: 12px;
|
|
727
|
+
margin-bottom: 20px;
|
|
728
|
+
}
|
|
729
|
+
.cost-summary-card {
|
|
730
|
+
background: var(--bg-input);
|
|
731
|
+
border-radius: 8px;
|
|
732
|
+
padding: 14px 16px;
|
|
733
|
+
border: 1px solid var(--border);
|
|
734
|
+
}
|
|
735
|
+
.cost-summary-label {
|
|
736
|
+
font-size: 11px;
|
|
737
|
+
color: var(--text-muted);
|
|
738
|
+
text-transform: uppercase;
|
|
739
|
+
letter-spacing: 0.5px;
|
|
740
|
+
margin-bottom: 4px;
|
|
741
|
+
}
|
|
742
|
+
.cost-summary-value {
|
|
743
|
+
font-family: var(--mono);
|
|
744
|
+
font-size: 20px;
|
|
745
|
+
font-weight: 700;
|
|
746
|
+
color: var(--accent);
|
|
747
|
+
}
|
|
748
|
+
.cost-summary-detail {
|
|
749
|
+
font-size: 11px;
|
|
750
|
+
color: var(--text-dim);
|
|
751
|
+
margin-top: 4px;
|
|
752
|
+
font-family: var(--mono);
|
|
753
|
+
}
|
|
754
|
+
.cost-strategy-cards {
|
|
755
|
+
display: grid;
|
|
756
|
+
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
|
|
757
|
+
gap: 16px;
|
|
758
|
+
margin-top: 16px;
|
|
759
|
+
}
|
|
760
|
+
.cost-strategy {
|
|
761
|
+
background: var(--bg-input);
|
|
762
|
+
border-radius: 10px;
|
|
763
|
+
padding: 18px;
|
|
764
|
+
border: 1px solid var(--border);
|
|
765
|
+
transition: border-color 0.2s, box-shadow 0.2s;
|
|
766
|
+
position: relative;
|
|
767
|
+
}
|
|
768
|
+
.cost-strategy.recommended {
|
|
769
|
+
border-color: var(--accent);
|
|
770
|
+
box-shadow: 0 0 16px var(--accent-glow);
|
|
771
|
+
}
|
|
772
|
+
.cost-strategy-badge {
|
|
773
|
+
position: absolute;
|
|
774
|
+
top: -10px;
|
|
775
|
+
right: 16px;
|
|
776
|
+
background: var(--accent);
|
|
777
|
+
color: var(--bg);
|
|
778
|
+
font-size: 10px;
|
|
779
|
+
font-weight: 700;
|
|
780
|
+
padding: 3px 10px;
|
|
781
|
+
border-radius: 10px;
|
|
782
|
+
text-transform: uppercase;
|
|
783
|
+
letter-spacing: 0.5px;
|
|
784
|
+
}
|
|
785
|
+
.cost-strategy-name {
|
|
786
|
+
font-size: 14px;
|
|
787
|
+
font-weight: 600;
|
|
788
|
+
color: var(--text);
|
|
789
|
+
margin-bottom: 12px;
|
|
790
|
+
}
|
|
791
|
+
.cost-strategy-row {
|
|
792
|
+
display: flex;
|
|
793
|
+
justify-content: space-between;
|
|
794
|
+
align-items: center;
|
|
795
|
+
padding: 4px 0;
|
|
796
|
+
font-size: 12px;
|
|
797
|
+
}
|
|
798
|
+
.cost-strategy-row-label { color: var(--text-dim); }
|
|
799
|
+
.cost-strategy-row-value { font-family: var(--mono); color: var(--text); font-weight: 500; }
|
|
800
|
+
.cost-strategy-total {
|
|
801
|
+
border-top: 1px solid var(--border);
|
|
802
|
+
margin-top: 10px;
|
|
803
|
+
padding-top: 10px;
|
|
804
|
+
display: flex;
|
|
805
|
+
justify-content: space-between;
|
|
806
|
+
align-items: center;
|
|
807
|
+
}
|
|
808
|
+
.cost-strategy-total-label { font-size: 13px; font-weight: 600; color: var(--text); }
|
|
809
|
+
.cost-strategy-total-value { font-family: var(--mono); font-size: 18px; font-weight: 700; color: var(--accent); }
|
|
810
|
+
.cost-savings {
|
|
811
|
+
font-size: 11px;
|
|
812
|
+
color: #4ade80;
|
|
813
|
+
font-weight: 600;
|
|
814
|
+
margin-top: 6px;
|
|
815
|
+
text-align: right;
|
|
816
|
+
}
|
|
668
817
|
.cost-table {
|
|
669
818
|
width: 100%;
|
|
670
819
|
border-collapse: collapse;
|
|
@@ -701,6 +850,24 @@ select:focus { outline: none; border-color: var(--accent); }
|
|
|
701
850
|
border-radius: 3px;
|
|
702
851
|
transition: width 0.4s ease;
|
|
703
852
|
}
|
|
853
|
+
.cost-section-title {
|
|
854
|
+
font-size: 13px;
|
|
855
|
+
font-weight: 600;
|
|
856
|
+
color: var(--text);
|
|
857
|
+
margin: 20px 0 8px;
|
|
858
|
+
display: flex;
|
|
859
|
+
align-items: center;
|
|
860
|
+
gap: 8px;
|
|
861
|
+
}
|
|
862
|
+
.cost-tip {
|
|
863
|
+
font-size: 12px;
|
|
864
|
+
color: var(--text-muted);
|
|
865
|
+
background: rgba(0, 212, 170, 0.05);
|
|
866
|
+
border-left: 3px solid var(--accent);
|
|
867
|
+
padding: 10px 14px;
|
|
868
|
+
border-radius: 0 6px 6px 0;
|
|
869
|
+
margin-top: 16px;
|
|
870
|
+
}
|
|
704
871
|
|
|
705
872
|
/* History chart */
|
|
706
873
|
.history-empty {
|
|
@@ -1184,30 +1351,116 @@ Reranking models rescore initial search results to improve relevance ordering.</
|
|
|
1184
1351
|
<!-- ── Cost Panel ── -->
|
|
1185
1352
|
<div class="bench-view" id="bench-cost">
|
|
1186
1353
|
<div class="card">
|
|
1187
|
-
<div class="card-title"
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
<
|
|
1354
|
+
<div class="card-title">💰 RAG Cost Calculator</div>
|
|
1355
|
+
|
|
1356
|
+
<!-- Mode toggle -->
|
|
1357
|
+
<div style="margin-bottom: 20px;">
|
|
1358
|
+
<div class="cost-mode-toggle">
|
|
1359
|
+
<button class="cost-mode-btn active" data-mode="simple" onclick="setCostMode('simple')">Simple</button>
|
|
1360
|
+
<button class="cost-mode-btn" data-mode="rag" onclick="setCostMode('rag')">RAG Planner</button>
|
|
1361
|
+
</div>
|
|
1192
1362
|
</div>
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
<
|
|
1363
|
+
|
|
1364
|
+
<!-- Simple mode (query cost comparison) -->
|
|
1365
|
+
<div id="costSimpleMode">
|
|
1366
|
+
<div class="cost-controls cost-controls-full">
|
|
1367
|
+
<div class="cost-slider-row">
|
|
1368
|
+
<span class="cost-slider-label">Tokens / query</span>
|
|
1369
|
+
<input type="range" class="cost-slider" id="costTokens" min="50" max="5000" value="500" step="50">
|
|
1370
|
+
<span class="cost-slider-value" id="costTokensValue">500</span>
|
|
1371
|
+
</div>
|
|
1372
|
+
<div class="cost-slider-row">
|
|
1373
|
+
<span class="cost-slider-label">Queries / day</span>
|
|
1374
|
+
<input type="range" class="cost-slider" id="costQueries" min="10" max="500000" value="1000" step="10">
|
|
1375
|
+
<span class="cost-slider-value" id="costQueriesValue">1,000</span>
|
|
1376
|
+
</div>
|
|
1377
|
+
</div>
|
|
1378
|
+
<table class="cost-table" id="costTable">
|
|
1379
|
+
<thead>
|
|
1380
|
+
<tr>
|
|
1381
|
+
<th>Model</th>
|
|
1382
|
+
<th>Type</th>
|
|
1383
|
+
<th>$/1M tokens</th>
|
|
1384
|
+
<th>Daily Cost</th>
|
|
1385
|
+
<th>Monthly Cost</th>
|
|
1386
|
+
<th style="width:30%">Relative</th>
|
|
1387
|
+
</tr>
|
|
1388
|
+
</thead>
|
|
1389
|
+
<tbody id="costTableBody"></tbody>
|
|
1390
|
+
</table>
|
|
1391
|
+
</div>
|
|
1392
|
+
|
|
1393
|
+
<!-- RAG Planner mode (full TCO) -->
|
|
1394
|
+
<div id="costRagMode" style="display:none;">
|
|
1395
|
+
<div class="cost-section-title">📄 Documents (one-time ingestion)</div>
|
|
1396
|
+
<div class="cost-controls">
|
|
1397
|
+
<div class="cost-slider-row">
|
|
1398
|
+
<span class="cost-slider-label">Documents</span>
|
|
1399
|
+
<input type="range" class="cost-slider" id="ragDocs" min="1000" max="10000000" value="100000" step="1000">
|
|
1400
|
+
<span class="cost-slider-value" id="ragDocsValue">100K</span>
|
|
1401
|
+
</div>
|
|
1402
|
+
<div class="cost-slider-row">
|
|
1403
|
+
<span class="cost-slider-label">Tokens / doc</span>
|
|
1404
|
+
<input type="range" class="cost-slider" id="ragDocTokens" min="50" max="5000" value="500" step="50">
|
|
1405
|
+
<span class="cost-slider-value" id="ragDocTokensValue">500</span>
|
|
1406
|
+
</div>
|
|
1407
|
+
</div>
|
|
1408
|
+
|
|
1409
|
+
<div class="cost-section-title">🔍 Queries (recurring)</div>
|
|
1410
|
+
<div class="cost-controls">
|
|
1411
|
+
<div class="cost-slider-row">
|
|
1412
|
+
<span class="cost-slider-label">Queries / month</span>
|
|
1413
|
+
<input type="range" class="cost-slider" id="ragQueries" min="1000" max="50000000" value="1000000" step="1000">
|
|
1414
|
+
<span class="cost-slider-value" id="ragQueriesValue">1M</span>
|
|
1415
|
+
</div>
|
|
1416
|
+
<div class="cost-slider-row">
|
|
1417
|
+
<span class="cost-slider-label">Tokens / query</span>
|
|
1418
|
+
<input type="range" class="cost-slider" id="ragQueryTokens" min="10" max="500" value="30" step="5">
|
|
1419
|
+
<span class="cost-slider-value" id="ragQueryTokensValue">30</span>
|
|
1420
|
+
</div>
|
|
1421
|
+
</div>
|
|
1422
|
+
|
|
1423
|
+
<div class="cost-section-title">⚙️ Configuration</div>
|
|
1424
|
+
<div class="cost-controls">
|
|
1425
|
+
<div class="cost-model-row">
|
|
1426
|
+
<span class="cost-model-label">Doc model</span>
|
|
1427
|
+
<select class="cost-select" id="ragDocModel"></select>
|
|
1428
|
+
</div>
|
|
1429
|
+
<div class="cost-model-row">
|
|
1430
|
+
<span class="cost-model-label">Query model</span>
|
|
1431
|
+
<select class="cost-select" id="ragQueryModel"></select>
|
|
1432
|
+
</div>
|
|
1433
|
+
<div class="cost-slider-row">
|
|
1434
|
+
<span class="cost-slider-label">Projection</span>
|
|
1435
|
+
<input type="range" class="cost-slider" id="ragMonths" min="1" max="36" value="12" step="1">
|
|
1436
|
+
<span class="cost-slider-value" id="ragMonthsValue">12 mo</span>
|
|
1437
|
+
</div>
|
|
1438
|
+
</div>
|
|
1439
|
+
|
|
1440
|
+
<!-- Summary cards -->
|
|
1441
|
+
<div class="cost-summary" id="ragSummary"></div>
|
|
1442
|
+
|
|
1443
|
+
<!-- Strategy comparison -->
|
|
1444
|
+
<div class="cost-section-title">📊 Strategy Comparison</div>
|
|
1445
|
+
<div class="cost-strategy-cards" id="ragStrategies"></div>
|
|
1446
|
+
|
|
1447
|
+
<!-- Per-model table -->
|
|
1448
|
+
<div class="cost-section-title">📋 Per-Model Breakdown</div>
|
|
1449
|
+
<table class="cost-table" id="ragTable">
|
|
1450
|
+
<thead>
|
|
1451
|
+
<tr>
|
|
1452
|
+
<th>Model</th>
|
|
1453
|
+
<th>Doc Cost</th>
|
|
1454
|
+
<th>Query $/mo</th>
|
|
1455
|
+
<th>Total (projected)</th>
|
|
1456
|
+
<th style="width:25%">Relative</th>
|
|
1457
|
+
</tr>
|
|
1458
|
+
</thead>
|
|
1459
|
+
<tbody id="ragTableBody"></tbody>
|
|
1460
|
+
</table>
|
|
1461
|
+
|
|
1462
|
+
<div class="cost-tip" id="ragTip"></div>
|
|
1197
1463
|
</div>
|
|
1198
|
-
<table class="cost-table" id="costTable">
|
|
1199
|
-
<thead>
|
|
1200
|
-
<tr>
|
|
1201
|
-
<th>Model</th>
|
|
1202
|
-
<th>Type</th>
|
|
1203
|
-
<th>$/1M tokens</th>
|
|
1204
|
-
<th>Daily Cost</th>
|
|
1205
|
-
<th>Monthly Cost</th>
|
|
1206
|
-
<th style="width:30%">Relative</th>
|
|
1207
|
-
</tr>
|
|
1208
|
-
</thead>
|
|
1209
|
-
<tbody id="costTableBody"></tbody>
|
|
1210
|
-
</table>
|
|
1211
1464
|
</div>
|
|
1212
1465
|
</div>
|
|
1213
1466
|
|
|
@@ -2326,7 +2579,49 @@ window.doBenchQuantization = async function() {
|
|
|
2326
2579
|
};
|
|
2327
2580
|
|
|
2328
2581
|
// ── Benchmark: Cost Calculator ──
|
|
2582
|
+
// ── Cost Calculator: Shared Helpers ──
|
|
2583
|
+
|
|
2584
|
+
function costFormatDollars(n) {
|
|
2585
|
+
if (n === 0) return '$0.00';
|
|
2586
|
+
if (n < 0.01 && n > 0) return '$' + n.toFixed(4);
|
|
2587
|
+
if (n < 1) return '$' + n.toFixed(2);
|
|
2588
|
+
return '$' + n.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
|
|
2589
|
+
}
|
|
2590
|
+
|
|
2591
|
+
function costShortNum(n) {
|
|
2592
|
+
if (n >= 1e9) return (n / 1e9).toFixed(n % 1e9 === 0 ? 0 : 1) + 'B';
|
|
2593
|
+
if (n >= 1e6) return (n / 1e6).toFixed(n % 1e6 === 0 ? 0 : 1) + 'M';
|
|
2594
|
+
if (n >= 1e3) return (n / 1e3).toFixed(n % 1e3 === 0 ? 0 : 1) + 'K';
|
|
2595
|
+
return String(n);
|
|
2596
|
+
}
|
|
2597
|
+
|
|
2598
|
+
function costGetPricePerM(model) {
|
|
2599
|
+
const match = model.price.match(/\$([0-9.]+)\/1M/);
|
|
2600
|
+
return match ? parseFloat(match[1]) : null;
|
|
2601
|
+
}
|
|
2602
|
+
|
|
2603
|
+
function costGetV4Models() {
|
|
2604
|
+
return allModels.filter(m => !m.legacy && !m.unreleased && costGetPricePerM(m) !== null);
|
|
2605
|
+
}
|
|
2606
|
+
|
|
2607
|
+
// ── Cost Mode Toggle ──
|
|
2608
|
+
|
|
2609
|
+
let currentCostMode = 'simple';
|
|
2610
|
+
|
|
2611
|
+
function setCostMode(mode) {
|
|
2612
|
+
currentCostMode = mode;
|
|
2613
|
+
document.querySelectorAll('.cost-mode-btn').forEach(btn => {
|
|
2614
|
+
btn.classList.toggle('active', btn.dataset.mode === mode);
|
|
2615
|
+
});
|
|
2616
|
+
document.getElementById('costSimpleMode').style.display = mode === 'simple' ? '' : 'none';
|
|
2617
|
+
document.getElementById('costRagMode').style.display = mode === 'rag' ? '' : 'none';
|
|
2618
|
+
if (mode === 'rag') updateRagCalculator();
|
|
2619
|
+
}
|
|
2620
|
+
|
|
2621
|
+
// ── Simple Mode (query-only comparison) ──
|
|
2622
|
+
|
|
2329
2623
|
function initCostCalculator() {
|
|
2624
|
+
// Simple mode sliders
|
|
2330
2625
|
const tokSlider = document.getElementById('costTokens');
|
|
2331
2626
|
const qSlider = document.getElementById('costQueries');
|
|
2332
2627
|
const tokValue = document.getElementById('costTokensValue');
|
|
@@ -2342,22 +2637,22 @@ function initCostCalculator() {
|
|
|
2342
2637
|
|
|
2343
2638
|
tokSlider.addEventListener('input', updateCost);
|
|
2344
2639
|
qSlider.addEventListener('input', updateCost);
|
|
2345
|
-
|
|
2346
|
-
// Initialize
|
|
2347
2640
|
updateCost();
|
|
2641
|
+
|
|
2642
|
+
// RAG mode init
|
|
2643
|
+
initRagCalculator();
|
|
2348
2644
|
}
|
|
2349
2645
|
|
|
2350
2646
|
function renderCostTable(tokensPerQuery, queriesPerDay) {
|
|
2351
2647
|
const tbody = document.getElementById('costTableBody');
|
|
2352
2648
|
tbody.innerHTML = '';
|
|
2353
2649
|
|
|
2354
|
-
const models = allModels.filter(m => !m.legacy);
|
|
2650
|
+
const models = allModels.filter(m => !m.legacy && !m.unreleased);
|
|
2355
2651
|
const rows = [];
|
|
2356
2652
|
|
|
2357
2653
|
models.forEach(m => {
|
|
2358
|
-
const
|
|
2359
|
-
if (
|
|
2360
|
-
const pricePerM = parseFloat(match[1]);
|
|
2654
|
+
const pricePerM = costGetPricePerM(m);
|
|
2655
|
+
if (pricePerM === null) return;
|
|
2361
2656
|
const dailyTokens = tokensPerQuery * queriesPerDay;
|
|
2362
2657
|
const dailyCost = (dailyTokens / 1_000_000) * pricePerM;
|
|
2363
2658
|
const monthlyCost = dailyCost * 30;
|
|
@@ -2388,6 +2683,234 @@ function renderCostTable(tokensPerQuery, queriesPerDay) {
|
|
|
2388
2683
|
});
|
|
2389
2684
|
}
|
|
2390
2685
|
|
|
2686
|
+
// ── RAG Planner Mode (full TCO with strategies) ──
|
|
2687
|
+
|
|
2688
|
+
function initRagCalculator() {
|
|
2689
|
+
// Populate model dropdowns
|
|
2690
|
+
const embeddingModels = allModels.filter(m =>
|
|
2691
|
+
m.type === 'embedding' && !m.legacy && !m.unreleased && costGetPricePerM(m) !== null
|
|
2692
|
+
);
|
|
2693
|
+
|
|
2694
|
+
const docSelect = document.getElementById('ragDocModel');
|
|
2695
|
+
const querySelect = document.getElementById('ragQueryModel');
|
|
2696
|
+
|
|
2697
|
+
embeddingModels.forEach(m => {
|
|
2698
|
+
const pricePerM = costGetPricePerM(m);
|
|
2699
|
+
const opt1 = document.createElement('option');
|
|
2700
|
+
opt1.value = m.name;
|
|
2701
|
+
opt1.textContent = `${m.name} ($${pricePerM.toFixed(2)}/1M)`;
|
|
2702
|
+
docSelect.appendChild(opt1);
|
|
2703
|
+
|
|
2704
|
+
const opt2 = document.createElement('option');
|
|
2705
|
+
opt2.value = m.name;
|
|
2706
|
+
opt2.textContent = `${m.name} ($${pricePerM.toFixed(2)}/1M)`;
|
|
2707
|
+
querySelect.appendChild(opt2);
|
|
2708
|
+
});
|
|
2709
|
+
|
|
2710
|
+
// Set defaults: voyage-4-large for docs, voyage-4-lite for queries
|
|
2711
|
+
docSelect.value = 'voyage-4-large';
|
|
2712
|
+
querySelect.value = 'voyage-4-lite';
|
|
2713
|
+
|
|
2714
|
+
// Bind all sliders and selects
|
|
2715
|
+
const ids = ['ragDocs', 'ragDocTokens', 'ragQueries', 'ragQueryTokens', 'ragMonths'];
|
|
2716
|
+
ids.forEach(id => {
|
|
2717
|
+
document.getElementById(id).addEventListener('input', updateRagCalculator);
|
|
2718
|
+
});
|
|
2719
|
+
docSelect.addEventListener('change', updateRagCalculator);
|
|
2720
|
+
querySelect.addEventListener('change', updateRagCalculator);
|
|
2721
|
+
|
|
2722
|
+
updateRagCalculator();
|
|
2723
|
+
}
|
|
2724
|
+
|
|
2725
|
+
function updateRagCalculator() {
|
|
2726
|
+
const numDocs = parseInt(document.getElementById('ragDocs').value, 10);
|
|
2727
|
+
const docTokens = parseInt(document.getElementById('ragDocTokens').value, 10);
|
|
2728
|
+
const numQueries = parseInt(document.getElementById('ragQueries').value, 10);
|
|
2729
|
+
const queryTokens = parseInt(document.getElementById('ragQueryTokens').value, 10);
|
|
2730
|
+
const months = parseInt(document.getElementById('ragMonths').value, 10);
|
|
2731
|
+
const docModelName = document.getElementById('ragDocModel').value;
|
|
2732
|
+
const queryModelName = document.getElementById('ragQueryModel').value;
|
|
2733
|
+
|
|
2734
|
+
// Update slider display values
|
|
2735
|
+
document.getElementById('ragDocsValue').textContent = costShortNum(numDocs);
|
|
2736
|
+
document.getElementById('ragDocTokensValue').textContent = docTokens.toLocaleString();
|
|
2737
|
+
document.getElementById('ragQueriesValue').textContent = costShortNum(numQueries);
|
|
2738
|
+
document.getElementById('ragQueryTokensValue').textContent = queryTokens.toLocaleString();
|
|
2739
|
+
document.getElementById('ragMonthsValue').textContent = months + ' mo';
|
|
2740
|
+
|
|
2741
|
+
const docTotalTokens = numDocs * docTokens;
|
|
2742
|
+
const queryTotalTokensPerMonth = numQueries * queryTokens;
|
|
2743
|
+
|
|
2744
|
+
// Get model prices
|
|
2745
|
+
const docModel = allModels.find(m => m.name === docModelName);
|
|
2746
|
+
const queryModel = allModels.find(m => m.name === queryModelName);
|
|
2747
|
+
const docPrice = docModel ? costGetPricePerM(docModel) : 0;
|
|
2748
|
+
const queryPrice = queryModel ? costGetPricePerM(queryModel) : 0;
|
|
2749
|
+
|
|
2750
|
+
// ── Build strategies (same logic as CLI) ──
|
|
2751
|
+
const strategies = [];
|
|
2752
|
+
const v4Embedding = allModels.filter(m =>
|
|
2753
|
+
m.type === 'embedding' && !m.legacy && !m.unreleased &&
|
|
2754
|
+
(m.sharedSpace === 'voyage-4' || m.name.startsWith('voyage-4')) &&
|
|
2755
|
+
costGetPricePerM(m) !== null && costGetPricePerM(m) > 0
|
|
2756
|
+
);
|
|
2757
|
+
|
|
2758
|
+
// Strategy group 1: Symmetric with each V4 model
|
|
2759
|
+
v4Embedding.forEach(m => {
|
|
2760
|
+
const price = costGetPricePerM(m);
|
|
2761
|
+
const docCost = (docTotalTokens / 1e6) * price;
|
|
2762
|
+
const queryCostPerMonth = (queryTotalTokensPerMonth / 1e6) * price;
|
|
2763
|
+
const totalCost = docCost + (queryCostPerMonth * months);
|
|
2764
|
+
strategies.push({
|
|
2765
|
+
name: `Symmetric: ${m.name}`,
|
|
2766
|
+
type: 'symmetric',
|
|
2767
|
+
docModel: m.name,
|
|
2768
|
+
queryModel: m.name,
|
|
2769
|
+
docCost,
|
|
2770
|
+
queryCostPerMonth,
|
|
2771
|
+
totalCost,
|
|
2772
|
+
months,
|
|
2773
|
+
});
|
|
2774
|
+
});
|
|
2775
|
+
|
|
2776
|
+
// Strategy 2: Asymmetric — user-selected combo
|
|
2777
|
+
const asymDocCost = (docTotalTokens / 1e6) * docPrice;
|
|
2778
|
+
const asymQueryCostPerMonth = (queryTotalTokensPerMonth / 1e6) * queryPrice;
|
|
2779
|
+
const asymTotalCost = asymDocCost + (asymQueryCostPerMonth * months);
|
|
2780
|
+
|
|
2781
|
+
// Only add if it's actually asymmetric (different models)
|
|
2782
|
+
if (docModelName !== queryModelName) {
|
|
2783
|
+
strategies.push({
|
|
2784
|
+
name: `Asymmetric: ${docModelName} + ${queryModelName}`,
|
|
2785
|
+
type: 'asymmetric',
|
|
2786
|
+
docModel: docModelName,
|
|
2787
|
+
queryModel: queryModelName,
|
|
2788
|
+
docCost: asymDocCost,
|
|
2789
|
+
queryCostPerMonth: asymQueryCostPerMonth,
|
|
2790
|
+
totalCost: asymTotalCost,
|
|
2791
|
+
months,
|
|
2792
|
+
recommended: true,
|
|
2793
|
+
});
|
|
2794
|
+
}
|
|
2795
|
+
|
|
2796
|
+
// Strategy 3: Asymmetric with nano (local, free queries)
|
|
2797
|
+
strategies.push({
|
|
2798
|
+
name: `Asymmetric: ${docModelName} + nano (local)`,
|
|
2799
|
+
type: 'asymmetric-local',
|
|
2800
|
+
docModel: docModelName,
|
|
2801
|
+
queryModel: 'voyage-4-nano',
|
|
2802
|
+
docCost: asymDocCost,
|
|
2803
|
+
queryCostPerMonth: 0,
|
|
2804
|
+
totalCost: asymDocCost,
|
|
2805
|
+
months,
|
|
2806
|
+
localNote: 'Query cost = $0 (runs locally via HuggingFace)',
|
|
2807
|
+
});
|
|
2808
|
+
|
|
2809
|
+
strategies.sort((a, b) => a.totalCost - b.totalCost);
|
|
2810
|
+
const maxCost = Math.max(...strategies.map(s => s.totalCost), 0.01);
|
|
2811
|
+
|
|
2812
|
+
// ── Render summary cards ──
|
|
2813
|
+
const summaryEl = document.getElementById('ragSummary');
|
|
2814
|
+
summaryEl.innerHTML = `
|
|
2815
|
+
<div class="cost-summary-card">
|
|
2816
|
+
<div class="cost-summary-label">Document tokens</div>
|
|
2817
|
+
<div class="cost-summary-value">${costShortNum(docTotalTokens)}</div>
|
|
2818
|
+
<div class="cost-summary-detail">${costShortNum(numDocs)} docs × ${docTokens.toLocaleString()} tok</div>
|
|
2819
|
+
</div>
|
|
2820
|
+
<div class="cost-summary-card">
|
|
2821
|
+
<div class="cost-summary-label">Query tokens / mo</div>
|
|
2822
|
+
<div class="cost-summary-value">${costShortNum(queryTotalTokensPerMonth)}</div>
|
|
2823
|
+
<div class="cost-summary-detail">${costShortNum(numQueries)} queries × ${queryTokens} tok</div>
|
|
2824
|
+
</div>
|
|
2825
|
+
<div class="cost-summary-card">
|
|
2826
|
+
<div class="cost-summary-label">Best ${months}-mo total</div>
|
|
2827
|
+
<div class="cost-summary-value">${costFormatDollars(strategies[0].totalCost)}</div>
|
|
2828
|
+
<div class="cost-summary-detail">${strategies[0].name}</div>
|
|
2829
|
+
</div>
|
|
2830
|
+
<div class="cost-summary-card">
|
|
2831
|
+
<div class="cost-summary-label">Max potential savings</div>
|
|
2832
|
+
<div class="cost-summary-value" style="color:#4ade80">${maxCost > 0 ? ((1 - strategies[0].totalCost / maxCost) * 100).toFixed(0) + '%' : '0%'}</div>
|
|
2833
|
+
<div class="cost-summary-detail">vs ${strategies[strategies.length - 1].name.split(':')[1]?.trim() || 'most expensive'}</div>
|
|
2834
|
+
</div>
|
|
2835
|
+
`;
|
|
2836
|
+
|
|
2837
|
+
// ── Render strategy cards ──
|
|
2838
|
+
const stratEl = document.getElementById('ragStrategies');
|
|
2839
|
+
stratEl.innerHTML = strategies.map(s => {
|
|
2840
|
+
const savings = maxCost > 0 ? ((1 - s.totalCost / maxCost) * 100) : 0;
|
|
2841
|
+
const savingsHtml = savings > 0 ? `<div class="cost-savings">↓ ${savings.toFixed(0)}% savings</div>` : '';
|
|
2842
|
+
const badgeHtml = s.recommended ? '<div class="cost-strategy-badge">★ Recommended</div>' : '';
|
|
2843
|
+
const localHtml = s.localNote ? `<div style="font-size:11px;color:var(--text-muted);margin-top:6px;">⚡ ${s.localNote}</div>` : '';
|
|
2844
|
+
|
|
2845
|
+
return `
|
|
2846
|
+
<div class="cost-strategy${s.recommended ? ' recommended' : ''}">
|
|
2847
|
+
${badgeHtml}
|
|
2848
|
+
<div class="cost-strategy-name">${s.name}</div>
|
|
2849
|
+
<div class="cost-strategy-row">
|
|
2850
|
+
<span class="cost-strategy-row-label">Doc embedding</span>
|
|
2851
|
+
<span class="cost-strategy-row-value">${costFormatDollars(s.docCost)} <span style="color:var(--text-muted);font-size:11px">(one-time)</span></span>
|
|
2852
|
+
</div>
|
|
2853
|
+
<div class="cost-strategy-row">
|
|
2854
|
+
<span class="cost-strategy-row-label">Query cost</span>
|
|
2855
|
+
<span class="cost-strategy-row-value">${costFormatDollars(s.queryCostPerMonth)}/mo</span>
|
|
2856
|
+
</div>
|
|
2857
|
+
<div class="cost-strategy-total">
|
|
2858
|
+
<span class="cost-strategy-total-label">${s.months}-month total</span>
|
|
2859
|
+
<span class="cost-strategy-total-value">${costFormatDollars(s.totalCost)}</span>
|
|
2860
|
+
</div>
|
|
2861
|
+
${savingsHtml}
|
|
2862
|
+
${localHtml}
|
|
2863
|
+
</div>
|
|
2864
|
+
`;
|
|
2865
|
+
}).join('');
|
|
2866
|
+
|
|
2867
|
+
// ── Render per-model table ──
|
|
2868
|
+
const ragTbody = document.getElementById('ragTableBody');
|
|
2869
|
+
ragTbody.innerHTML = '';
|
|
2870
|
+
|
|
2871
|
+
const allEmbedding = allModels.filter(m =>
|
|
2872
|
+
m.type === 'embedding' && !m.legacy && !m.unreleased && costGetPricePerM(m) !== null && costGetPricePerM(m) > 0
|
|
2873
|
+
);
|
|
2874
|
+
|
|
2875
|
+
const tableRows = allEmbedding.map(m => {
|
|
2876
|
+
const price = costGetPricePerM(m);
|
|
2877
|
+
const dCost = (docTotalTokens / 1e6) * price;
|
|
2878
|
+
const qCostMo = (queryTotalTokensPerMonth / 1e6) * price;
|
|
2879
|
+
const total = dCost + (qCostMo * months);
|
|
2880
|
+
return { name: m.name, dCost, qCostMo, total };
|
|
2881
|
+
}).sort((a, b) => a.total - b.total);
|
|
2882
|
+
|
|
2883
|
+
const maxTable = Math.max(...tableRows.map(r => r.total), 0.01);
|
|
2884
|
+
|
|
2885
|
+
tableRows.forEach(r => {
|
|
2886
|
+
const tr = document.createElement('tr');
|
|
2887
|
+
const barPct = Math.max(2, (r.total / maxTable) * 100);
|
|
2888
|
+
tr.innerHTML = `
|
|
2889
|
+
<td style="color:var(--text)">${r.name}</td>
|
|
2890
|
+
<td>${costFormatDollars(r.dCost)}</td>
|
|
2891
|
+
<td>${costFormatDollars(r.qCostMo)}</td>
|
|
2892
|
+
<td class="cost-highlight">${costFormatDollars(r.total)}</td>
|
|
2893
|
+
<td class="cost-bar-cell" style="position:relative;padding-left:8px;">
|
|
2894
|
+
<div class="cost-bar" style="width:${barPct}%;"></div>
|
|
2895
|
+
<span style="position:relative;z-index:1;font-size:12px;color:var(--text-dim);">${costFormatDollars(r.total)}</span>
|
|
2896
|
+
</td>
|
|
2897
|
+
`;
|
|
2898
|
+
ragTbody.appendChild(tr);
|
|
2899
|
+
});
|
|
2900
|
+
|
|
2901
|
+
// ── Tip ──
|
|
2902
|
+
const bestSym = strategies.find(s => s.type === 'symmetric' && s.docModel === 'voyage-4-large');
|
|
2903
|
+
const bestAsym = strategies.find(s => s.recommended);
|
|
2904
|
+
const tipEl = document.getElementById('ragTip');
|
|
2905
|
+
if (bestSym && bestAsym && bestSym.totalCost > bestAsym.totalCost) {
|
|
2906
|
+
const saved = bestSym.totalCost - bestAsym.totalCost;
|
|
2907
|
+
const pct = ((saved / bestSym.totalCost) * 100).toFixed(0);
|
|
2908
|
+
tipEl.innerHTML = `💡 <strong>Asymmetric retrieval saves ${costFormatDollars(saved)} (${pct}%)</strong> over symmetric voyage-4-large — same document quality, lower query costs. The shared embedding space makes this possible. <code>vai explain shared-space</code>`;
|
|
2909
|
+
} else {
|
|
2910
|
+
tipEl.innerHTML = '💡 Try selecting different doc and query models to see asymmetric cost savings. <code>vai explain shared-space</code>';
|
|
2911
|
+
}
|
|
2912
|
+
}
|
|
2913
|
+
|
|
2391
2914
|
// ── Benchmark: History ──
|
|
2392
2915
|
const HISTORY_KEY = 'vai-bench-history';
|
|
2393
2916
|
|