ai-speedometer 2.1.6 → 2.1.7
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/dist/ai-speedometer +133 -21
- package/package.json +1 -1
package/dist/ai-speedometer
CHANGED
|
@@ -1712,7 +1712,7 @@ var exports_benchmark = {};
|
|
|
1712
1712
|
__export(exports_benchmark, {
|
|
1713
1713
|
benchmarkSingleModelRest: () => benchmarkSingleModelRest
|
|
1714
1714
|
});
|
|
1715
|
-
async function benchmarkSingleModelRest(model) {
|
|
1715
|
+
async function benchmarkSingleModelRest(model, logger) {
|
|
1716
1716
|
try {
|
|
1717
1717
|
if (!model.providerConfig || !model.providerConfig.apiKey) {
|
|
1718
1718
|
throw new Error(`Missing API key for provider ${model.providerName}`);
|
|
@@ -1729,6 +1729,7 @@ async function benchmarkSingleModelRest(model) {
|
|
|
1729
1729
|
actualModelId = model.name;
|
|
1730
1730
|
}
|
|
1731
1731
|
actualModelId = actualModelId.trim();
|
|
1732
|
+
await logger?.logHeader(model.name, model.providerName, model.providerConfig.apiKey);
|
|
1732
1733
|
const startTime = Date.now();
|
|
1733
1734
|
let firstTokenTime = null;
|
|
1734
1735
|
let streamedText = "";
|
|
@@ -1801,21 +1802,21 @@ async function benchmarkSingleModelRest(model) {
|
|
|
1801
1802
|
const reader = response.body.getReader();
|
|
1802
1803
|
const decoder = new TextDecoder;
|
|
1803
1804
|
let buffer = "";
|
|
1804
|
-
let
|
|
1805
|
+
let firstParsedTokenTime = null;
|
|
1805
1806
|
while (true) {
|
|
1806
1807
|
const { done, value } = await reader.read();
|
|
1807
1808
|
if (done)
|
|
1808
1809
|
break;
|
|
1809
|
-
if (
|
|
1810
|
+
if (!firstTokenTime)
|
|
1810
1811
|
firstTokenTime = Date.now();
|
|
1811
|
-
isFirstChunk = false;
|
|
1812
|
-
}
|
|
1813
1812
|
buffer += decoder.decode(value, { stream: true });
|
|
1814
1813
|
const lines = buffer.split(`
|
|
1815
1814
|
`);
|
|
1816
1815
|
buffer = lines.pop() || "";
|
|
1817
1816
|
for (const line of lines) {
|
|
1818
1817
|
const trimmedLine = line.trim();
|
|
1818
|
+
if (trimmedLine)
|
|
1819
|
+
await logger?.logRaw(trimmedLine);
|
|
1819
1820
|
if (!trimmedLine)
|
|
1820
1821
|
continue;
|
|
1821
1822
|
try {
|
|
@@ -1827,6 +1828,8 @@ async function benchmarkSingleModelRest(model) {
|
|
|
1827
1828
|
const chunk = JSON.parse(jsonStr);
|
|
1828
1829
|
const chunkTyped = chunk;
|
|
1829
1830
|
if (chunkTyped.type === "content_block_delta" && chunkTyped.delta?.text) {
|
|
1831
|
+
if (!firstParsedTokenTime)
|
|
1832
|
+
firstParsedTokenTime = Date.now();
|
|
1830
1833
|
streamedText += chunkTyped.delta.text;
|
|
1831
1834
|
} else if (chunkTyped.type === "message_start" && chunkTyped.message?.usage) {
|
|
1832
1835
|
inputTokens = chunkTyped.message.usage.input_tokens || 0;
|
|
@@ -1841,6 +1844,8 @@ async function benchmarkSingleModelRest(model) {
|
|
|
1841
1844
|
} else {
|
|
1842
1845
|
const chunk = JSON.parse(trimmedLine);
|
|
1843
1846
|
if (chunk.type === "content_block_delta" && chunk.delta?.text) {
|
|
1847
|
+
if (!firstParsedTokenTime)
|
|
1848
|
+
firstParsedTokenTime = Date.now();
|
|
1844
1849
|
streamedText += chunk.delta.text;
|
|
1845
1850
|
} else if (chunk.type === "message_start" && chunk.message?.usage) {
|
|
1846
1851
|
inputTokens = chunk.message.usage.input_tokens || 0;
|
|
@@ -1854,6 +1859,8 @@ async function benchmarkSingleModelRest(model) {
|
|
|
1854
1859
|
} else if (model.providerType === "google") {
|
|
1855
1860
|
const chunk = JSON.parse(trimmedLine);
|
|
1856
1861
|
if (chunk.candidates?.[0]?.content?.parts?.[0]?.text) {
|
|
1862
|
+
if (!firstParsedTokenTime)
|
|
1863
|
+
firstParsedTokenTime = Date.now();
|
|
1857
1864
|
streamedText += chunk.candidates[0].content.parts[0].text;
|
|
1858
1865
|
}
|
|
1859
1866
|
if (chunk.usageMetadata?.promptTokenCount)
|
|
@@ -1866,10 +1873,15 @@ async function benchmarkSingleModelRest(model) {
|
|
|
1866
1873
|
if (jsonStr === "[DONE]")
|
|
1867
1874
|
continue;
|
|
1868
1875
|
const chunk = JSON.parse(jsonStr);
|
|
1869
|
-
if (chunk.choices?.[0]?.delta?.content)
|
|
1876
|
+
if (chunk.choices?.[0]?.delta?.content) {
|
|
1877
|
+
if (!firstParsedTokenTime)
|
|
1878
|
+
firstParsedTokenTime = Date.now();
|
|
1870
1879
|
streamedText += chunk.choices[0].delta.content;
|
|
1871
|
-
else if (chunk.choices?.[0]?.delta?.reasoning)
|
|
1880
|
+
} else if (chunk.choices?.[0]?.delta?.reasoning) {
|
|
1881
|
+
if (!firstParsedTokenTime)
|
|
1882
|
+
firstParsedTokenTime = Date.now();
|
|
1872
1883
|
streamedText += chunk.choices[0].delta.reasoning;
|
|
1884
|
+
}
|
|
1873
1885
|
if (chunk.usage?.prompt_tokens)
|
|
1874
1886
|
inputTokens = chunk.usage.prompt_tokens;
|
|
1875
1887
|
if (chunk.usage?.completion_tokens)
|
|
@@ -1881,15 +1893,18 @@ async function benchmarkSingleModelRest(model) {
|
|
|
1881
1893
|
}
|
|
1882
1894
|
}
|
|
1883
1895
|
}
|
|
1896
|
+
await logger?.flush();
|
|
1884
1897
|
const endTime = Date.now();
|
|
1885
1898
|
const totalTime = endTime - startTime;
|
|
1886
|
-
const
|
|
1899
|
+
const effectiveFirstToken = firstParsedTokenTime ?? firstTokenTime;
|
|
1900
|
+
const timeToFirstToken = effectiveFirstToken ? effectiveFirstToken - startTime : totalTime;
|
|
1901
|
+
const generationTime = totalTime - timeToFirstToken;
|
|
1887
1902
|
const usedEstimateForOutput = !outputTokens;
|
|
1888
1903
|
const usedEstimateForInput = !inputTokens;
|
|
1889
1904
|
const finalOutputTokens = outputTokens || Math.round(streamedText.length / 4);
|
|
1890
1905
|
const finalInputTokens = inputTokens || Math.round(TEST_PROMPT.length / 4);
|
|
1891
1906
|
const totalTokens = finalInputTokens + finalOutputTokens;
|
|
1892
|
-
const tokensPerSecond =
|
|
1907
|
+
const tokensPerSecond = generationTime > 0 ? finalOutputTokens / generationTime * 1000 : 0;
|
|
1893
1908
|
return {
|
|
1894
1909
|
model: model.name,
|
|
1895
1910
|
provider: model.providerName,
|
|
@@ -1904,6 +1919,7 @@ async function benchmarkSingleModelRest(model) {
|
|
|
1904
1919
|
success: true
|
|
1905
1920
|
};
|
|
1906
1921
|
} catch (error) {
|
|
1922
|
+
await logger?.flush();
|
|
1907
1923
|
return {
|
|
1908
1924
|
model: model.name,
|
|
1909
1925
|
provider: model.providerName,
|
|
@@ -2085,6 +2101,66 @@ var init_headless = __esm(() => {
|
|
|
2085
2101
|
init_benchmark();
|
|
2086
2102
|
});
|
|
2087
2103
|
|
|
2104
|
+
// ../core/src/logger.ts
|
|
2105
|
+
var exports_logger = {};
|
|
2106
|
+
__export(exports_logger, {
|
|
2107
|
+
getLogPath: () => getLogPath,
|
|
2108
|
+
createRunId: () => createRunId,
|
|
2109
|
+
createBenchLogger: () => createBenchLogger
|
|
2110
|
+
});
|
|
2111
|
+
import { mkdir, appendFile } from "fs/promises";
|
|
2112
|
+
import { homedir as homedir4 } from "os";
|
|
2113
|
+
import { join } from "path";
|
|
2114
|
+
function generateRunId() {
|
|
2115
|
+
const now = new Date;
|
|
2116
|
+
const date = now.toISOString().slice(0, 10);
|
|
2117
|
+
const time = now.toTimeString().slice(0, 8).replace(/:/g, "");
|
|
2118
|
+
const rand = Math.random().toString(16).slice(2, 6);
|
|
2119
|
+
return `${date}_${time}_${rand}`;
|
|
2120
|
+
}
|
|
2121
|
+
function redactSecrets(line, apiKey) {
|
|
2122
|
+
if (!apiKey)
|
|
2123
|
+
return line;
|
|
2124
|
+
return line.split(apiKey).join("[REDACTED]");
|
|
2125
|
+
}
|
|
2126
|
+
function createRunId() {
|
|
2127
|
+
return generateRunId();
|
|
2128
|
+
}
|
|
2129
|
+
function getLogPath(runId) {
|
|
2130
|
+
return join(homedir4(), ".local", "share", "ai-speedometer", "logs", `${runId}.log`);
|
|
2131
|
+
}
|
|
2132
|
+
async function createBenchLogger(runId) {
|
|
2133
|
+
const logPath = getLogPath(runId);
|
|
2134
|
+
const logDir = join(homedir4(), ".local", "share", "ai-speedometer", "logs");
|
|
2135
|
+
await mkdir(logDir, { recursive: true });
|
|
2136
|
+
let currentApiKey = "";
|
|
2137
|
+
let buffer = "";
|
|
2138
|
+
return {
|
|
2139
|
+
logPath,
|
|
2140
|
+
runId,
|
|
2141
|
+
logHeader: async (modelName, providerName, apiKey = "") => {
|
|
2142
|
+
currentApiKey = apiKey;
|
|
2143
|
+
const ts = new Date().toISOString();
|
|
2144
|
+
buffer = `
|
|
2145
|
+
=== ${modelName} | ${providerName} | ${ts} ===
|
|
2146
|
+
`;
|
|
2147
|
+
},
|
|
2148
|
+
logRaw: async (line) => {
|
|
2149
|
+
const safe = redactSecrets(line, currentApiKey);
|
|
2150
|
+
buffer += safe + `
|
|
2151
|
+
`;
|
|
2152
|
+
},
|
|
2153
|
+
flush: async () => {
|
|
2154
|
+
buffer += `
|
|
2155
|
+
` + "=".repeat(60) + `
|
|
2156
|
+
`;
|
|
2157
|
+
await appendFile(logPath, buffer, "utf8");
|
|
2158
|
+
buffer = "";
|
|
2159
|
+
}
|
|
2160
|
+
};
|
|
2161
|
+
}
|
|
2162
|
+
var init_logger = () => {};
|
|
2163
|
+
|
|
2088
2164
|
// src/tui/context/AppContext.tsx
|
|
2089
2165
|
import { createContext, useContext, useReducer, useEffect } from "react";
|
|
2090
2166
|
import { jsxDEV } from "@opentui/react/jsx-dev-runtime";
|
|
@@ -2121,12 +2197,23 @@ function appReducer(state, action) {
|
|
|
2121
2197
|
};
|
|
2122
2198
|
case "BENCH_RESET":
|
|
2123
2199
|
return { ...state, benchResults: [], selectedModels: [] };
|
|
2200
|
+
case "SET_LOG_INFO":
|
|
2201
|
+
return { ...state, logMode: action.logMode, logPath: action.logPath, runId: action.runId };
|
|
2124
2202
|
default:
|
|
2125
2203
|
return state;
|
|
2126
2204
|
}
|
|
2127
2205
|
}
|
|
2128
|
-
function AppProvider({ children }) {
|
|
2206
|
+
function AppProvider({ children, logMode = false }) {
|
|
2129
2207
|
const [state, dispatch] = useReducer(appReducer, initialState);
|
|
2208
|
+
useEffect(() => {
|
|
2209
|
+
if (logMode) {
|
|
2210
|
+
Promise.resolve().then(() => (init_logger(), exports_logger)).then(({ createRunId: createRunId2, getLogPath: getLogPath2 }) => {
|
|
2211
|
+
const runId = createRunId2();
|
|
2212
|
+
const logPath = getLogPath2(runId);
|
|
2213
|
+
dispatch({ type: "SET_LOG_INFO", logMode: true, logPath, runId });
|
|
2214
|
+
});
|
|
2215
|
+
}
|
|
2216
|
+
}, [logMode]);
|
|
2130
2217
|
useEffect(() => {
|
|
2131
2218
|
let cancelled = false;
|
|
2132
2219
|
async function loadConfig2() {
|
|
@@ -2169,7 +2256,10 @@ var init_AppContext = __esm(() => {
|
|
|
2169
2256
|
config: null,
|
|
2170
2257
|
selectedModels: [],
|
|
2171
2258
|
benchResults: [],
|
|
2172
|
-
isLoadingConfig: true
|
|
2259
|
+
isLoadingConfig: true,
|
|
2260
|
+
logMode: false,
|
|
2261
|
+
logPath: null,
|
|
2262
|
+
runId: null
|
|
2173
2263
|
};
|
|
2174
2264
|
AppContext = createContext(null);
|
|
2175
2265
|
});
|
|
@@ -2179,7 +2269,7 @@ var package_default;
|
|
|
2179
2269
|
var init_package = __esm(() => {
|
|
2180
2270
|
package_default = {
|
|
2181
2271
|
name: "ai-speedometer",
|
|
2182
|
-
version: "2.1.
|
|
2272
|
+
version: "2.1.7",
|
|
2183
2273
|
description: "A comprehensive CLI tool for benchmarking AI models across multiple providers with parallel execution and professional metrics",
|
|
2184
2274
|
bin: {
|
|
2185
2275
|
"ai-speedometer": "dist/ai-speedometer",
|
|
@@ -3005,9 +3095,12 @@ function BenchmarkScreen() {
|
|
|
3005
3095
|
setModelStates((prev) => prev.map((s) => ({ ...s, status: "running", startedAt: Date.now() })));
|
|
3006
3096
|
async function runAll() {
|
|
3007
3097
|
const { benchmarkSingleModelRest: benchmarkSingleModelRest2 } = await Promise.resolve().then(() => (init_benchmark(), exports_benchmark));
|
|
3098
|
+
const logEnabled = state.logMode && !!state.runId;
|
|
3099
|
+
const { createBenchLogger: createBenchLogger2 } = logEnabled ? await Promise.resolve().then(() => (init_logger(), exports_logger)) : { createBenchLogger: null };
|
|
3008
3100
|
const promises = models.map(async (model) => {
|
|
3101
|
+
const logger = logEnabled && createBenchLogger2 ? await createBenchLogger2(state.runId) : undefined;
|
|
3009
3102
|
try {
|
|
3010
|
-
const result = await benchmarkSingleModelRest2(model);
|
|
3103
|
+
const result = await benchmarkSingleModelRest2(model, logger);
|
|
3011
3104
|
if (!result.success) {
|
|
3012
3105
|
const errMsg = result.error ?? "Request failed";
|
|
3013
3106
|
setModelStates((prev) => prev.map((s) => s.model.id === model.id && s.model.providerId === model.providerId ? { ...s, status: "error", error: errMsg } : s));
|
|
@@ -3428,10 +3521,22 @@ function BenchmarkScreen() {
|
|
|
3428
3521
|
navigate("main-menu");
|
|
3429
3522
|
}
|
|
3430
3523
|
});
|
|
3431
|
-
const statusLine = allDone ? /* @__PURE__ */ jsxDEV10("
|
|
3432
|
-
|
|
3433
|
-
children:
|
|
3434
|
-
|
|
3524
|
+
const statusLine = allDone ? /* @__PURE__ */ jsxDEV10("box", {
|
|
3525
|
+
flexDirection: "row",
|
|
3526
|
+
children: [
|
|
3527
|
+
/* @__PURE__ */ jsxDEV10("text", {
|
|
3528
|
+
fg: "#9ece6a",
|
|
3529
|
+
children: "All done! [Enter]/[Q] return [\u2191\u2193/PgUp/PgDn/wheel] scroll"
|
|
3530
|
+
}, undefined, false, undefined, this),
|
|
3531
|
+
state.logMode && state.logPath && /* @__PURE__ */ jsxDEV10("text", {
|
|
3532
|
+
fg: "#565f89",
|
|
3533
|
+
children: [
|
|
3534
|
+
" log: ",
|
|
3535
|
+
state.logPath
|
|
3536
|
+
]
|
|
3537
|
+
}, undefined, true, undefined, this)
|
|
3538
|
+
]
|
|
3539
|
+
}, undefined, true, undefined, this) : /* @__PURE__ */ jsxDEV10("box", {
|
|
3435
3540
|
flexDirection: "row",
|
|
3436
3541
|
children: [
|
|
3437
3542
|
running.length > 0 && /* @__PURE__ */ jsxDEV10("text", {
|
|
@@ -5100,8 +5205,9 @@ function Shell() {
|
|
|
5100
5205
|
]
|
|
5101
5206
|
}, undefined, true, undefined, this);
|
|
5102
5207
|
}
|
|
5103
|
-
function App() {
|
|
5208
|
+
function App({ logMode = false }) {
|
|
5104
5209
|
return /* @__PURE__ */ jsxDEV15(AppProvider, {
|
|
5210
|
+
logMode,
|
|
5105
5211
|
children: /* @__PURE__ */ jsxDEV15(Shell, {}, undefined, false, undefined, this)
|
|
5106
5212
|
}, undefined, false, undefined, this);
|
|
5107
5213
|
}
|
|
@@ -5127,7 +5233,7 @@ __export(exports_tui, {
|
|
|
5127
5233
|
import { createCliRenderer } from "@opentui/core";
|
|
5128
5234
|
import { createRoot } from "@opentui/react";
|
|
5129
5235
|
import { jsxDEV as jsxDEV16 } from "@opentui/react/jsx-dev-runtime";
|
|
5130
|
-
async function startTui() {
|
|
5236
|
+
async function startTui(logMode = false) {
|
|
5131
5237
|
const renderer = await createCliRenderer({
|
|
5132
5238
|
exitOnCtrlC: false
|
|
5133
5239
|
});
|
|
@@ -5141,7 +5247,9 @@ async function startTui() {
|
|
|
5141
5247
|
renderer.destroy();
|
|
5142
5248
|
process.exit(0);
|
|
5143
5249
|
});
|
|
5144
|
-
createRoot(renderer).render(/* @__PURE__ */ jsxDEV16(App, {
|
|
5250
|
+
createRoot(renderer).render(/* @__PURE__ */ jsxDEV16(App, {
|
|
5251
|
+
logMode
|
|
5252
|
+
}, undefined, false, undefined, this));
|
|
5145
5253
|
}
|
|
5146
5254
|
var ENABLE_BRACKETED_PASTE = "\x1B[?2004h", DISABLE_BRACKETED_PASTE = "\x1B[?2004l";
|
|
5147
5255
|
var init_tui = __esm(() => {
|
|
@@ -5153,6 +5261,7 @@ function parseCliArgs() {
|
|
|
5153
5261
|
const args = process.argv.slice(2);
|
|
5154
5262
|
const parsed = {
|
|
5155
5263
|
debug: false,
|
|
5264
|
+
log: false,
|
|
5156
5265
|
bench: null,
|
|
5157
5266
|
benchCustom: null,
|
|
5158
5267
|
apiKey: null,
|
|
@@ -5165,6 +5274,8 @@ function parseCliArgs() {
|
|
|
5165
5274
|
const arg = args[i];
|
|
5166
5275
|
if (arg === "--debug")
|
|
5167
5276
|
parsed.debug = true;
|
|
5277
|
+
else if (arg === "--log")
|
|
5278
|
+
parsed.log = true;
|
|
5168
5279
|
else if (arg === "--bench")
|
|
5169
5280
|
parsed.bench = args[++i] ?? null;
|
|
5170
5281
|
else if (arg === "--bench-custom")
|
|
@@ -5197,6 +5308,7 @@ function showHelp() {
|
|
|
5197
5308
|
console.log(" --api-key <key> API key for custom provider");
|
|
5198
5309
|
console.log(" --endpoint-format <format> Endpoint format (default: chat/completions)");
|
|
5199
5310
|
console.log(" --formatted Format JSON output for human readability");
|
|
5311
|
+
console.log(" --log Log raw SSE streams to ~/.local/share/ai-speedometer/logs/");
|
|
5200
5312
|
console.log(" --debug Enable debug logging");
|
|
5201
5313
|
console.log(" --help, -h Show this help message");
|
|
5202
5314
|
console.log("");
|
|
@@ -5214,5 +5326,5 @@ if (cliArgs.help) {
|
|
|
5214
5326
|
await runHeadlessBenchmark2(cliArgs);
|
|
5215
5327
|
} else {
|
|
5216
5328
|
const { startTui: startTui2 } = await Promise.resolve().then(() => (init_tui(), exports_tui));
|
|
5217
|
-
await startTui2();
|
|
5329
|
+
await startTui2(cliArgs.log);
|
|
5218
5330
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ai-speedometer",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.7",
|
|
4
4
|
"description": "A comprehensive CLI tool for benchmarking AI models across multiple providers with parallel execution and professional metrics",
|
|
5
5
|
"bin": {
|
|
6
6
|
"ai-speedometer": "dist/ai-speedometer",
|