

























11// Bench Model script supports OpenClaw repository automation.
2+import { pathToFileURL } from "node:url";
23import { completeSimple, type Model } from "openclaw/plugin-sdk/llm";
4+import { parseStrictIntegerOption } from "./lib/dev-tooling-safety.ts";
3546type Usage = {
57input?: number;
@@ -14,26 +16,87 @@ type RunResult = {
1416usage?: Usage;
1517};
161819+type CliOptions = {
20+help: boolean;
21+prompt: string;
22+runs: number;
23+};
24+1725const DEFAULT_PROMPT = "Reply with a single word: ok. No punctuation or extra text.";
1826const DEFAULT_RUNS = 10;
27+const BOOLEAN_FLAGS = new Set(["--help", "-h"]);
28+const VALUE_FLAGS = new Set(["--prompt", "--runs"]);
192920-function parseArg(flag: string): string | undefined {
21-const idx = process.argv.indexOf(flag);
22-if (idx === -1) {
23-return undefined;
30+class CliArgumentError extends Error {
31+override name = "CliArgumentError";
32+}
33+34+function readValue(argv: string[], index: number, flag: string): string {
35+const value = argv[index + 1]?.trim() ?? "";
36+if (!value || value.startsWith("--")) {
37+throw new CliArgumentError(`${flag} requires a value`);
2438}
25-return process.argv[idx + 1];
39+return value;
2640}
274128-function parseRuns(raw: string | undefined): number {
29-if (!raw) {
30-return DEFAULT_RUNS;
42+function validateCliArgs(argv: string[]): void {
43+for (let index = 0; index < argv.length; index += 1) {
44+const arg = argv[index] ?? "";
45+if (BOOLEAN_FLAGS.has(arg)) {
46+continue;
47+}
48+if (VALUE_FLAGS.has(arg)) {
49+readValue(argv, index, arg);
50+index += 1;
51+continue;
52+}
53+throw new CliArgumentError(`Unknown argument: ${arg}`);
3154}
32-const parsed = Number(raw);
33-if (!Number.isFinite(parsed) || parsed <= 0) {
34-return DEFAULT_RUNS;
55+}
56+57+function parseArg(argv: string[], flag: string): string | undefined {
58+const index = argv.indexOf(flag);
59+if (index === -1) {
60+return undefined;
3561}
36-return Math.floor(parsed);
62+return readValue(argv, index, flag);
63+}
64+65+function parseRuns(raw: string | undefined): number {
66+return parseStrictIntegerOption({
67+fallback: DEFAULT_RUNS,
68+label: "--runs",
69+min: 1,
70+ raw,
71+});
72+}
73+74+function parseArgs(argv = process.argv.slice(2)): CliOptions {
75+validateCliArgs(argv);
76+return {
77+help: argv.includes("--help") || argv.includes("-h"),
78+prompt: parseArg(argv, "--prompt") ?? DEFAULT_PROMPT,
79+runs: parseRuns(parseArg(argv, "--runs")),
80+};
81+}
82+83+function printUsage(): void {
84+console.log(`OpenClaw model latency benchmark
85+86+Usage:
87+ node --import tsx scripts/bench-model.ts [options]
88+89+Options:
90+ --runs <n> Runs per model (default: ${DEFAULT_RUNS})
91+ --prompt <text> Prompt to send to each model
92+ --help, -h Show this text
93+94+Environment:
95+ ANTHROPIC_API_KEY
96+ MINIMAX_API_KEY
97+ MINIMAX_BASE_URL
98+ MINIMAX_MODEL
99+`);
37100}
3810139102function median(values: number[]): number {
@@ -78,9 +141,12 @@ async function runModel(opts: {
78141return results;
79142}
8014381-async function main(): Promise<void> {
82-const runs = parseRuns(parseArg("--runs"));
83-const prompt = parseArg("--prompt") ?? DEFAULT_PROMPT;
144+async function main(argv = process.argv.slice(2)): Promise<void> {
145+const options = parseArgs(argv);
146+if (options.help) {
147+printUsage();
148+return;
149+}
8415085151const anthropicKey = process.env.ANTHROPIC_API_KEY?.trim();
86152const minimaxKey = process.env.MINIMAX_API_KEY?.trim();
@@ -118,23 +184,23 @@ async function main(): Promise<void> {
118184maxTokens: 32000,
119185};
120186121-console.log(`Prompt: ${prompt}`);
122-console.log(`Runs: ${runs}`);
187+console.log(`Prompt: ${options.prompt}`);
188+console.log(`Runs: ${options.runs}`);
123189console.log("");
124190125191const minimaxResults = await runModel({
126192label: "minimax",
127193model: minimaxModel,
128194apiKey: minimaxKey,
129- runs,
130- prompt,
195+runs: options.runs,
196+prompt: options.prompt,
131197});
132198const opusResults = await runModel({
133199label: "opus",
134200model: opusModel,
135201apiKey: anthropicKey,
136- runs,
137- prompt,
202+runs: options.runs,
203+prompt: options.prompt,
138204});
139205140206const summarize = (label: string, results: RunResult[]) => {
@@ -153,4 +219,21 @@ async function main(): Promise<void> {
153219}
154220}
155221156-await main();
222+export const testing = {
223+ median,
224+ parseArgs,
225+ parseRuns,
226+ validateCliArgs,
227+};
228+229+if (import.meta.url === pathToFileURL(process.argv[1] ?? "").href) {
230+main().catch((err: unknown) => {
231+if (err instanceof CliArgumentError) {
232+console.error(err.message);
233+process.exitCode = 1;
234+return;
235+}
236+console.error(err instanceof Error ? err.stack : String(err));
237+process.exitCode = 1;
238+});
239+}
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。