rtr-bench 0.1.1 → 0.1.3
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 +53 -27
- package/dist/cli.js +126 -18
- package/package.json +2 -1
- package/results/leaderboard-scatter.png +0 -0
package/README.md
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
# RTR-bench
|
|
2
2
|
|
|
3
|
-
[Mixed Messages](https://mixedmessages.fyi)
|
|
3
|
+
**RTR-bench** is a benchmark that scores language models on cultural fluency, built from real human data. Its source is [Mixed Messages](https://mixedmessages.fyi), a generative art project that asks participants to interpret a daily prompt by placing a coordinate on a 2x2 grid, where the x-axis is Honest vs Performed and the y-axis is Uniting vs Dividing. Across 174 closed windows there have been almost 40k individual submissions. Each one is a unique, real human data point.
|
|
4
4
|
|
|
5
5
|
Most labeled datasets are made by asking annotators to apply a rubric. This one was not. Nobody was given a definition of honest or performed, nobody was trained, and there was no correct answer to converge on. People arrived, interpreted a prompt, placed a submission, and left. What makes the data unusual is that the crowd's answer is a distribution, rather than a label. Not "people say X", but "31% of people said this, 44% said that, and the rest said something else."
|
|
6
6
|
|
|
7
|
-
That distribution is what makes it useful for evaluating a model. Every window has a
|
|
7
|
+
That distribution is what makes it useful for evaluating a model. Every window has a dominant quadrant, so a model can be scored on whether it lands with the crowd or against it, and on which axis it misreads when it does. There is also nothing here to memorize: the axes are cultural rather than factual, so no amount of training data tells a model how a crowd interpreted a prompt. The only way to get it right is for the model to understand how people would think and interpret.
|
|
8
8
|
|
|
9
|
-
Commonly-cited benchmarks, such as [SWE-bench](https://www.swebench.com/) and [HLE](https://lastexam.ai/),
|
|
9
|
+
Commonly-cited benchmarks, such as [SWE-bench](https://www.swebench.com/) and [HLE](https://lastexam.ai/), measure whether a model can produce a correct answer: multiple choice, math, and code. RTR-bench measures a different subject. There is no answer key in this data, only how people actually interpreted an ambiguous prompt, so a model is scored against the crowd rather than against a key. That is what makes it a test of whether a model can read the room.
|
|
10
10
|
|
|
11
|
-
**RTR-bench
|
|
11
|
+
**RTR-bench** is not a knowledge test, and that is deliberate. It is a prediction task. Given a prompt, a model has to decide how the crowd interpreted it, and then decide how it would interpret it itself. Both answers are scored against the real submissions.
|
|
12
12
|
|
|
13
13
|
## How it works
|
|
14
14
|
|
|
@@ -29,28 +29,45 @@ Commonly-cited benchmarks, such as [SWE-bench](https://www.swebench.com/) and [H
|
|
|
29
29
|
|
|
30
30
|
## Quickstart
|
|
31
31
|
|
|
32
|
-
Scoring needs no database access.
|
|
32
|
+
Scoring needs no database access, just an OpenRouter API key.
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
git clone https://github.com/1999labs/RTR-bench
|
|
36
|
-
cd RTR-bench
|
|
37
|
-
npm install
|
|
34
|
+
### Test a model
|
|
38
35
|
|
|
39
|
-
|
|
36
|
+
Node 18 or newer. No clone, no install.
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
mkdir rtr-run && cd rtr-run
|
|
40
40
|
echo 'OPENROUTER_API_KEY=sk-or-your-key' > .env
|
|
41
41
|
|
|
42
|
-
#
|
|
43
|
-
|
|
42
|
+
# see ids and prices. costs nothing
|
|
43
|
+
npx --yes rtr-bench catalog --limit 20
|
|
44
44
|
|
|
45
|
-
# run
|
|
46
|
-
|
|
47
|
-
node src/cli.ts report
|
|
45
|
+
# run one model, then score it
|
|
46
|
+
npx --yes rtr-bench z-ai/glm-5.3-flash
|
|
48
47
|
```
|
|
49
48
|
|
|
50
|
-
|
|
49
|
+
`--yes` skips npx's install prompt. A run makes 1,320 calls, takes about four minutes on a cheap model, costs a few cents, and writes `results/` in the folder you created. Add `--concurrency 8` to go faster.
|
|
50
|
+
|
|
51
|
+
By default OpenRouter may route each call to any host that serves the model, and hosts differ in quantisation. List them, with price and precision:
|
|
51
52
|
|
|
52
53
|
```bash
|
|
53
|
-
npx rtr-bench
|
|
54
|
+
npx --yes rtr-bench providers z-ai/glm-5.3-flash
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Pin one with `--provider Wafer`. A pinned run refuses to fall back, and the summary names the host that served the calls.
|
|
58
|
+
|
|
59
|
+
### Work on the code
|
|
60
|
+
|
|
61
|
+
The repository runs the TypeScript sources directly, so this path needs Node 22.18 or newer.
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
git clone https://github.com/1999labs/RTR-bench
|
|
65
|
+
cd RTR-bench
|
|
66
|
+
npm install
|
|
67
|
+
echo 'OPENROUTER_API_KEY=sk-or-your-key' > .env
|
|
68
|
+
node src/cli.ts catalog --limit 20
|
|
69
|
+
node src/cli.ts run --models z-ai/glm-5.3-flash --concurrency 8
|
|
70
|
+
node src/cli.ts report
|
|
54
71
|
```
|
|
55
72
|
|
|
56
73
|
Rebuilding the dataset needs the project's database connection, so `npm run export` is included for inspection only.
|
|
@@ -59,16 +76,25 @@ Rebuilding the dataset needs the project's database connection, so `npm run expo
|
|
|
59
76
|
|
|
60
77
|
| Model | Provider | Score | Cost |
|
|
61
78
|
|---|---|---|---|
|
|
62
|
-
| z-ai/glm-5.3-flash |
|
|
63
|
-
| z-ai/glm-5.3 |
|
|
64
|
-
| minimax/minimax-m3.1-flash |
|
|
65
|
-
|
|
|
66
|
-
|
|
|
67
|
-
|
|
|
68
|
-
| google/gemini-
|
|
69
|
-
|
|
|
70
|
-
|
|
|
71
|
-
|
|
|
79
|
+
| z-ai/glm-5.3-flash | Tenstorrent (fp8) | 41.2 | $0.0281 |
|
|
80
|
+
| z-ai/glm-5.3 | Tenstorrent (fp8) | 40.0 | $0.2828 |
|
|
81
|
+
| minimax/minimax-m3.1-flash | GMICloud (fp8) | 32.1 | $0.2444 |
|
|
82
|
+
| xiaomi/mimo-v2.6-flash | GMICloud (bf16) | 40.6 | $0.1216 |
|
|
83
|
+
| deepseek/deepseek-v4.1-flash-1017 | Morph (fp8) | 40.0 | $0.3414 |
|
|
84
|
+
| tencent/hy3 | Tencent (fp8) | 40.0 | $0.2665 |
|
|
85
|
+
| google/gemini-3.8-flash | Google AI Studio | 46.1 | $0.7790 |
|
|
86
|
+
| openai/gpt-6-luna | OpenAI | 37.0 | $0.1329 |
|
|
87
|
+
| anthropic/claude-haiku-5.5 | Claude Platform on AWS | 43.0 | $0.1696 |
|
|
88
|
+
| thinkingmachines/inkling-small | DeepInfra (fp8) | 37.0 | $0.1733 |
|
|
89
|
+
| openai/gpt-oss-120b | DekaLLM (bf16) | 32.7 | $0.0205 |
|
|
90
|
+
|
|
91
|
+

|
|
92
|
+
|
|
93
|
+
Eleven models were run against the same 165 windows, and all of them landed between 32.1 and 46.1 out of 100. The floors sit low: guessing among four readings scores 25, and always naming the crowd's most common quadrant scores 31.5. Nearly every model clears both, and none of them comes close to reading the room. The order at the top is not a ranking either. At 165 windows the confidence intervals are about seven points wide, so gemini-3.8-flash at 46.1 and claude-haiku-5.5 at 43.0 are the same result measured twice, once for 78 cents and once for 17.
|
|
94
|
+
|
|
95
|
+
The models are more alike in how they fail than in how they score. Across 1,591 answers they name honest+uniting 67.7 percent of the time and honest+dividing 18.2 percent of the time, while the crowd chose honest+dividing in 39 of the 165 windows. Division, when a model registers it at all, reads as spin or as bad faith, and almost never as candour. Six of the seven windows that no model got right are honest+dividing, among them Donald Trump and Elon Musk, where the crowd agreed with itself 86 and 70 percent. The 78-cent model and the two-cent one miss the same quadrant.
|
|
96
|
+
|
|
97
|
+
Cost barely enters into it. Two cents buys 32.7, seventeen cents buys 43.0, seventy-eight cents buys 46.1, and the whole table cost $2.56 to produce. What the numbers describe is a set of models that can tell whether a word feels warm or hostile, and cannot tell what this particular audience would do with it. That gap is the thing the benchmark was built to measure, and eleven models later it is still open.
|
|
72
98
|
|
|
73
99
|
## License
|
|
74
100
|
|
package/dist/cli.js
CHANGED
|
@@ -120,7 +120,8 @@ function readModel(dir) {
|
|
|
120
120
|
order: rec.order,
|
|
121
121
|
task,
|
|
122
122
|
parsed: rec.parsed ?? null,
|
|
123
|
-
cost: rec.usage?.cost ?? rec.usage?.total_cost ?? 0
|
|
123
|
+
cost: rec.usage?.cost ?? rec.usage?.total_cost ?? 0,
|
|
124
|
+
provider: rec.provider ?? null
|
|
124
125
|
});
|
|
125
126
|
}
|
|
126
127
|
}
|
|
@@ -236,6 +237,10 @@ function scoreModel(model, windows) {
|
|
|
236
237
|
const mean = (xs) => xs.length ? xs.reduce((a, b) => a + b, 0) / xs.length : NaN;
|
|
237
238
|
const predictAcc = mean(predHits);
|
|
238
239
|
const readAgree = mean(readHits);
|
|
240
|
+
const providers = {};
|
|
241
|
+
for (const c of calls) {
|
|
242
|
+
if (c.provider) providers[c.provider] = (providers[c.provider] ?? 0) + 1;
|
|
243
|
+
}
|
|
239
244
|
return {
|
|
240
245
|
model: display,
|
|
241
246
|
windows: predHits.length,
|
|
@@ -247,7 +252,8 @@ function scoreModel(model, windows) {
|
|
|
247
252
|
gap: readAgree - predictAcc,
|
|
248
253
|
instability: pred.length ? pred.filter((a) => a.unstable).length / pred.length : NaN,
|
|
249
254
|
parseFailures: calls.filter((c) => !c.parsed).length,
|
|
250
|
-
costUsd: calls.reduce((a, c) => a + c.cost, 0)
|
|
255
|
+
costUsd: calls.reduce((a, c) => a + c.cost, 0),
|
|
256
|
+
providers
|
|
251
257
|
};
|
|
252
258
|
}
|
|
253
259
|
function majorityBaseline(windows) {
|
|
@@ -313,19 +319,30 @@ import fs3 from "node:fs";
|
|
|
313
319
|
import path2 from "node:path";
|
|
314
320
|
var API = "https://openrouter.ai/api/v1/chat/completions";
|
|
315
321
|
var apiKey = () => process.env.OPENROUTER_API_KEY ?? "";
|
|
322
|
+
var nextSlot = 0;
|
|
323
|
+
async function pace(rpm) {
|
|
324
|
+
if (!rpm) return;
|
|
325
|
+
const gap = 6e4 / rpm;
|
|
326
|
+
const now = Date.now();
|
|
327
|
+
const start = Math.max(now, nextSlot);
|
|
328
|
+
nextSlot = start + gap;
|
|
329
|
+
const wait = start - now;
|
|
330
|
+
if (wait > 0) await new Promise((r) => setTimeout(r, wait));
|
|
331
|
+
}
|
|
316
332
|
function slug(model) {
|
|
317
333
|
return model.replace(/[^a-zA-Z0-9]+/g, "-").toLowerCase();
|
|
318
334
|
}
|
|
319
|
-
async function call(model, task, word, order) {
|
|
335
|
+
async function call(model, task, word, order, provider) {
|
|
320
336
|
const messages = buildMessages(task, word, order);
|
|
321
337
|
const body = {
|
|
322
338
|
model,
|
|
323
339
|
messages,
|
|
324
340
|
temperature: 0,
|
|
325
341
|
reasoning: { effort: "low" },
|
|
326
|
-
max_tokens:
|
|
342
|
+
max_tokens: 2e3,
|
|
327
343
|
usage: { include: true }
|
|
328
344
|
};
|
|
345
|
+
if (provider) body.provider = { order: [provider], allow_fallbacks: false };
|
|
329
346
|
const post = async () => fetch(API, {
|
|
330
347
|
method: "POST",
|
|
331
348
|
headers: {
|
|
@@ -360,12 +377,32 @@ async function call(model, task, word, order) {
|
|
|
360
377
|
const text = await res.text();
|
|
361
378
|
throw new Error(`HTTP ${status}: ${text.slice(0, 300)}`);
|
|
362
379
|
}
|
|
363
|
-
const
|
|
380
|
+
const j0 = await res.json();
|
|
381
|
+
let j = j0;
|
|
382
|
+
const noContent = !(j0?.choices?.[0]?.message?.content ?? "") && j0?.choices?.[0]?.finish_reason === "length";
|
|
383
|
+
if (noContent) {
|
|
384
|
+
const bigger = { ...body, max_tokens: 4e3 };
|
|
385
|
+
const r2 = await fetch(API, {
|
|
386
|
+
method: "POST",
|
|
387
|
+
headers: {
|
|
388
|
+
Authorization: `Bearer ${apiKey()}`,
|
|
389
|
+
"Content-Type": "application/json",
|
|
390
|
+
"HTTP-Referer": "https://mixedmessages.fyi",
|
|
391
|
+
"X-Title": "RTR-bench"
|
|
392
|
+
},
|
|
393
|
+
body: JSON.stringify(bigger)
|
|
394
|
+
});
|
|
395
|
+
if (r2.ok) {
|
|
396
|
+
j = await r2.json();
|
|
397
|
+
body.max_tokens = 4e3;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
364
400
|
return {
|
|
365
401
|
json: j,
|
|
366
402
|
body,
|
|
367
403
|
temperature: "temperature" in body ? 0 : null,
|
|
368
|
-
reasoningEffort: body.reasoning?.effort ?? null
|
|
404
|
+
reasoningEffort: body.reasoning?.effort ?? null,
|
|
405
|
+
maxTokens: body.max_tokens
|
|
369
406
|
};
|
|
370
407
|
}
|
|
371
408
|
async function withRetry(fn, tries = 4) {
|
|
@@ -388,11 +425,31 @@ async function runModel(windows, opts) {
|
|
|
388
425
|
throw new Error(
|
|
389
426
|
"OPENROUTER_API_KEY is not set. Put it in .env in this directory, or export it in your shell."
|
|
390
427
|
);
|
|
391
|
-
|
|
428
|
+
try {
|
|
429
|
+
const res = await fetch(`${API.replace("/chat/completions", "")}/credits`, {
|
|
430
|
+
headers: { Authorization: `Bearer ${apiKey()}` }
|
|
431
|
+
});
|
|
432
|
+
if (res.ok) {
|
|
433
|
+
const d = (await res.json()).data;
|
|
434
|
+
const remaining = d.total_credits - d.total_usage;
|
|
435
|
+
if (remaining <= 0)
|
|
436
|
+
throw new Error(
|
|
437
|
+
`OpenRouter balance is $${remaining.toFixed(4)}. Add credit at https://openrouter.ai/settings/credits, then rerun.`
|
|
438
|
+
);
|
|
439
|
+
}
|
|
440
|
+
} catch (e) {
|
|
441
|
+
if (String(e.message).startsWith("OpenRouter balance")) throw e;
|
|
442
|
+
}
|
|
443
|
+
const dir = path2.join(
|
|
444
|
+
"results",
|
|
445
|
+
"raw",
|
|
446
|
+
slug(opts.model) + (opts.provider ? `@${slug(opts.provider)}` : "")
|
|
447
|
+
);
|
|
392
448
|
let done = 0;
|
|
393
449
|
let failed = 0;
|
|
394
450
|
let tokens = 0;
|
|
395
451
|
let usd = 0;
|
|
452
|
+
const served = {};
|
|
396
453
|
const list = opts.limit ? windows.slice(0, opts.limit) : windows;
|
|
397
454
|
const jobs = [];
|
|
398
455
|
for (const w of list) {
|
|
@@ -403,7 +460,17 @@ async function runModel(windows, opts) {
|
|
|
403
460
|
}
|
|
404
461
|
}
|
|
405
462
|
const file2 = (j) => path2.join(dir, j.task, `${j.day}-${j.order}.json`);
|
|
406
|
-
const
|
|
463
|
+
const want = opts.provider ?? null;
|
|
464
|
+
const isDone = (f) => {
|
|
465
|
+
if (!fs3.existsSync(f)) return false;
|
|
466
|
+
try {
|
|
467
|
+
const rec = JSON.parse(fs3.readFileSync(f, "utf8"));
|
|
468
|
+
return !rec.error && rec.parsed && (rec.providerRequested ?? null) === want;
|
|
469
|
+
} catch {
|
|
470
|
+
return false;
|
|
471
|
+
}
|
|
472
|
+
};
|
|
473
|
+
const queue = jobs.filter((j) => !isDone(file2(j)));
|
|
407
474
|
console.log(
|
|
408
475
|
`${opts.model}: ${jobs.length} calls, ${jobs.length - queue.length} already on disk, ${queue.length} to go`
|
|
409
476
|
);
|
|
@@ -415,13 +482,15 @@ async function runModel(windows, opts) {
|
|
|
415
482
|
fs3.mkdirSync(path2.dirname(target), { recursive: true });
|
|
416
483
|
let rec;
|
|
417
484
|
try {
|
|
418
|
-
|
|
419
|
-
|
|
485
|
+
await pace(opts.rpm);
|
|
486
|
+
const { json, body, temperature, reasoningEffort, maxTokens } = await withRetry(
|
|
487
|
+
() => call(opts.model, j.task, j.word, j.order, opts.provider)
|
|
420
488
|
);
|
|
421
489
|
const raw = json?.choices?.[0]?.message?.content ?? "";
|
|
422
490
|
const usage = json?.usage ?? {};
|
|
423
491
|
tokens += usage.total_tokens ?? 0;
|
|
424
|
-
usd += Number(
|
|
492
|
+
usd += Number(usage.cost ?? usage.total_cost ?? 0);
|
|
493
|
+
if (json?.provider) served[json.provider] = (served[json.provider] ?? 0) + 1;
|
|
425
494
|
rec = {
|
|
426
495
|
model: opts.model,
|
|
427
496
|
day: j.day,
|
|
@@ -431,6 +500,8 @@ async function runModel(windows, opts) {
|
|
|
431
500
|
promptHash: PROMPT_HASH,
|
|
432
501
|
temperature,
|
|
433
502
|
reasoningEffort,
|
|
503
|
+
providerRequested: opts.provider ?? null,
|
|
504
|
+
maxTokens,
|
|
434
505
|
request: body,
|
|
435
506
|
raw,
|
|
436
507
|
parsed: parseAnswer(raw),
|
|
@@ -450,6 +521,8 @@ async function runModel(windows, opts) {
|
|
|
450
521
|
promptHash: PROMPT_HASH,
|
|
451
522
|
temperature: 0,
|
|
452
523
|
reasoningEffort: null,
|
|
524
|
+
providerRequested: opts.provider ?? null,
|
|
525
|
+
maxTokens: null,
|
|
453
526
|
request: null,
|
|
454
527
|
raw: "",
|
|
455
528
|
parsed: null,
|
|
@@ -470,8 +543,29 @@ async function runModel(windows, opts) {
|
|
|
470
543
|
}
|
|
471
544
|
};
|
|
472
545
|
await Promise.all(Array.from({ length: opts.concurrency }, worker));
|
|
546
|
+
const names = Object.keys(served);
|
|
547
|
+
const mix = names.length ? names.map((n) => `${n} ${served[n]}`).join(", ") : "unknown";
|
|
473
548
|
console.log(`${opts.model}: wrote ${done}, failed ${failed}, tokens ${tokens.toLocaleString()}`);
|
|
474
|
-
|
|
549
|
+
console.log(`${opts.model}: providers ${mix}${opts.provider ? " (pinned)" : ""}`);
|
|
550
|
+
return { done, failed, tokens, usd, providers: served };
|
|
551
|
+
}
|
|
552
|
+
async function listEndpoints(model) {
|
|
553
|
+
const res = await fetch(`${API.replace("/chat/completions", "")}/models/${model}/endpoints`, {
|
|
554
|
+
headers: { "User-Agent": "RTR-bench", Accept: "application/json" }
|
|
555
|
+
});
|
|
556
|
+
if (!res.ok) throw new Error(`endpoints HTTP ${res.status}`);
|
|
557
|
+
const j = await res.json();
|
|
558
|
+
const eps = j.data?.endpoints ?? [];
|
|
559
|
+
return eps.map((e) => ({
|
|
560
|
+
provider: e.provider ?? e.provider_name ?? "?",
|
|
561
|
+
quant: e.quantization ?? "?",
|
|
562
|
+
inUsd: +(Number(e.pricing?.prompt) * 1e6).toFixed(3),
|
|
563
|
+
outUsd: +(Number(e.pricing?.completion) * 1e6).toFixed(3),
|
|
564
|
+
ctx: e.context_length ?? null,
|
|
565
|
+
status: e.status ?? null
|
|
566
|
+
})).sort(
|
|
567
|
+
(a, b) => (a.status === 0 ? 0 : 1) - (b.status === 0 ? 0 : 1) || a.inUsd - b.inUsd
|
|
568
|
+
);
|
|
475
569
|
}
|
|
476
570
|
async function listModels() {
|
|
477
571
|
const res = await fetch("https://openrouter.ai/api/v1/models", {
|
|
@@ -533,12 +627,12 @@ function writeReport() {
|
|
|
533
627
|
lines.push("## Leaderboard");
|
|
534
628
|
lines.push("");
|
|
535
629
|
lines.push(
|
|
536
|
-
"| model | score /100 | n | cost | 95% CI | axis x | axis y | Brier | own reading agrees | crowd distance | gap | unstable |"
|
|
630
|
+
"| model | score /100 | n | cost | provider | 95% CI | axis x | axis y | Brier | own reading agrees | crowd distance | gap | unstable |"
|
|
537
631
|
);
|
|
538
|
-
lines.push("
|
|
632
|
+
lines.push("|---|---|---|---|---|---|---|---|---|---|---|---|---|");
|
|
539
633
|
for (const s of ranked) {
|
|
540
634
|
lines.push(
|
|
541
|
-
`| \`${s.model}\` | **${(100 * s.predict.acc).toFixed(1)}** | ${s.windows} | $${s.costUsd.toFixed(4)} | ${ci(s.predict.ci)} | ${pct(s.axes.x)} | ${pct(s.axes.y)} | ${round(s.brier)} | ${pct(s.read.agree)} | ${round(s.crowdDistance)} | ${s.gap >= 0 ? "+" : ""}${pct(s.gap)} | ${pct(s.instability)} |`
|
|
635
|
+
`| \`${s.model}\` | **${(100 * s.predict.acc).toFixed(1)}** | ${s.windows} | $${s.costUsd.toFixed(4)} | ${providerLabel(s)} | ${ci(s.predict.ci)} | ${pct(s.axes.x)} | ${pct(s.axes.y)} | ${round(s.brier)} | ${pct(s.read.agree)} | ${round(s.crowdDistance)} | ${s.gap >= 0 ? "+" : ""}${pct(s.gap)} | ${pct(s.instability)} |`
|
|
542
636
|
);
|
|
543
637
|
}
|
|
544
638
|
lines.push("");
|
|
@@ -611,6 +705,12 @@ function writeReport() {
|
|
|
611
705
|
console.log("wrote results/leaderboard.md and results/leaderboard.svg");
|
|
612
706
|
return data;
|
|
613
707
|
}
|
|
708
|
+
function providerLabel(s) {
|
|
709
|
+
const names = Object.keys(s.providers);
|
|
710
|
+
if (names.length === 0) return "?";
|
|
711
|
+
if (names.length === 1) return names[0];
|
|
712
|
+
return `${names.length} providers`;
|
|
713
|
+
}
|
|
614
714
|
function scoreLine(s) {
|
|
615
715
|
const [lo, hi] = s.predict.ci;
|
|
616
716
|
return [
|
|
@@ -618,7 +718,8 @@ function scoreLine(s) {
|
|
|
618
718
|
`${(100 * s.predict.acc).toFixed(1)} / 100`,
|
|
619
719
|
`${s.windows} windows`,
|
|
620
720
|
`95% CI ${(100 * lo).toFixed(1)} to ${(100 * hi).toFixed(1)}`,
|
|
621
|
-
`cost $${s.costUsd.toFixed(4)}
|
|
721
|
+
`cost $${s.costUsd.toFixed(4)}`,
|
|
722
|
+
`provider ${providerLabel(s)}`
|
|
622
723
|
].join(" ");
|
|
623
724
|
}
|
|
624
725
|
function svg(scores, baselines, windowCount) {
|
|
@@ -719,8 +820,10 @@ if (cmd === "catalog") {
|
|
|
719
820
|
const windows = loadWindows().map((w) => ({ day: w.day, word: w.word }));
|
|
720
821
|
const limit = flags.limit ? Number(flags.limit) : void 0;
|
|
721
822
|
const concurrency = Number(flags.concurrency ?? 6);
|
|
823
|
+
const rpm = flags.rpm ? Number(flags.rpm) : void 0;
|
|
824
|
+
const provider = flags.provider ? String(flags.provider) : void 0;
|
|
722
825
|
for (const model of models) {
|
|
723
|
-
await runModel(windows, { model, tasks: [...tasks], concurrency, limit });
|
|
826
|
+
await runModel(windows, { model, tasks: [...tasks], concurrency, limit, provider, rpm });
|
|
724
827
|
}
|
|
725
828
|
const data = writeReport();
|
|
726
829
|
for (const s of data.scores) {
|
|
@@ -744,11 +847,16 @@ if (cmd === "catalog") {
|
|
|
744
847
|
model: cmd,
|
|
745
848
|
tasks: ["predict", "read"],
|
|
746
849
|
concurrency: Number(flags.concurrency ?? 6),
|
|
747
|
-
limit: flags.limit ? Number(flags.limit) : void 0
|
|
850
|
+
limit: flags.limit ? Number(flags.limit) : void 0,
|
|
851
|
+
provider: flags.provider ? String(flags.provider) : void 0
|
|
748
852
|
});
|
|
749
853
|
const data = writeReport();
|
|
750
854
|
const s = data.scores.find((x) => x.model === cmd);
|
|
751
855
|
if (s) console.log(scoreLine(s));
|
|
856
|
+
} else if (cmd === "providers") {
|
|
857
|
+
const id = rest.find((a) => !a.startsWith("--"));
|
|
858
|
+
if (!id) throw new Error("usage: rtr-bench providers <model-id>");
|
|
859
|
+
console.table(await listEndpoints(id));
|
|
752
860
|
} else {
|
|
753
861
|
const here = path4.dirname(fileURLToPath2(import.meta.url));
|
|
754
862
|
console.log(fs5.readFileSync(path4.join(here, "..", "README.md"), "utf8").slice(0, 1200));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rtr-bench",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "Can a language model predict how a crowd reads a word? A cultural fluency benchmark built from Mixed Messages.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
"files": [
|
|
26
26
|
"dist/",
|
|
27
27
|
"data/",
|
|
28
|
+
"results/leaderboard-scatter.png",
|
|
28
29
|
"README.md",
|
|
29
30
|
"LICENSE"
|
|
30
31
|
],
|
|
Binary file
|