localarena 0.1.0 → 0.2.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 +22 -0
- package/README.md +252 -140
- package/docs/provider-and-task-support.md +165 -0
- package/examples/evaluation-config.json +157 -0
- package/javascript/src/evaluation.js +1712 -0
- package/javascript/src/index.d.ts +545 -0
- package/javascript/src/index.js +49 -0
- package/javascript/src/providers.js +1303 -0
- package/javascript/src/report.js +355 -0
- package/javascript/src/tasks.js +828 -0
- package/package.json +7 -3
- package/schema/localarena-evaluation-config-v1.schema.json +612 -0
- package/schema/localarena-evaluation-run-v1.schema.json +1422 -0
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,28 @@ All notable changes to LocalArena are documented here.
|
|
|
4
4
|
|
|
5
5
|
The project follows [Semantic Versioning](https://semver.org/).
|
|
6
6
|
|
|
7
|
+
## [0.2.0] - 2026-07-26
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- Live OpenAI-compatible providers for llama.cpp, Ollama, LM Studio,
|
|
12
|
+
OpenRouter, OpenAI, and custom base URLs.
|
|
13
|
+
- Concurrent model-by-task evaluation with exact, containment, regular
|
|
14
|
+
expression, JSON, numeric, and live model-judge scoring.
|
|
15
|
+
- A Python CLI for provider discovery, live runs, saved results, and
|
|
16
|
+
standalone HTML reports.
|
|
17
|
+
- Shared evaluation configuration and run schemas, privacy-safe result
|
|
18
|
+
serialization, and judge-only model configuration.
|
|
19
|
+
- Matching JavaScript APIs, TypeScript declarations, normalized usage data,
|
|
20
|
+
bounded transport behavior, and cancellation.
|
|
21
|
+
|
|
22
|
+
### Changed
|
|
23
|
+
|
|
24
|
+
- Prompt, answer, evaluator, and diagnostic content is excluded from saved
|
|
25
|
+
results by default and requires an explicit opt-in.
|
|
26
|
+
- Generation retries are disabled by default to avoid duplicating ambiguous
|
|
27
|
+
provider requests or billing.
|
|
28
|
+
|
|
7
29
|
## [0.1.0] - 2026-07-25
|
|
8
30
|
|
|
9
31
|
### Added
|
package/README.md
CHANGED
|
@@ -1,210 +1,322 @@
|
|
|
1
|
-
#
|
|
1
|
+
# LocalArena
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
Run the models you choose against the tasks you choose, using live provider
|
|
4
|
+
responses from your machine or an explicitly configured API endpoint.
|
|
5
5
|
|
|
6
6
|
[](https://pypi.org/project/localarena/)
|
|
7
7
|
[](https://www.npmjs.com/package/localarena)
|
|
8
8
|
[](LICENSE)
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
leaderboard.
|
|
10
|
+
LocalArena executes the complete model × task grid concurrently, applies
|
|
11
|
+
deterministic scorers or a live judge model, records failures as data, derives
|
|
12
|
+
Elo standings, and writes a standalone HTML report. It never substitutes
|
|
13
|
+
recorded answers or copied leaderboard results for a live call.
|
|
14
14
|
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
-
|
|
19
|
-
-
|
|
20
|
-
-
|
|
15
|
+
- llama.cpp, Ollama, and LM Studio on loopback or any reachable host
|
|
16
|
+
- OpenRouter and OpenAI with separate runtime credentials
|
|
17
|
+
- Any compatible Chat Completions endpoint through a custom base URL
|
|
18
|
+
- Python CLI and library, plus matching JavaScript and TypeScript APIs
|
|
19
|
+
- Exact, contains, regular-expression, JSON, numeric, and model-judge scoring
|
|
20
|
+
- Privacy-safe result files by default: prompts and answers require opt-in
|
|
21
|
+
- No runtime dependencies in either package
|
|
21
22
|
|
|
22
23
|
## Install
|
|
23
24
|
|
|
24
|
-
Python
|
|
25
|
+
Python 3.10+:
|
|
25
26
|
|
|
26
27
|
```bash
|
|
27
28
|
pip install localarena
|
|
28
29
|
```
|
|
29
30
|
|
|
30
|
-
|
|
31
|
+
Node.js 18+:
|
|
31
32
|
|
|
32
33
|
```bash
|
|
33
34
|
npm install localarena
|
|
34
35
|
```
|
|
35
36
|
|
|
36
|
-
##
|
|
37
|
+
## Run a live evaluation
|
|
37
38
|
|
|
38
|
-
|
|
39
|
+
Create `evaluation.json`:
|
|
39
40
|
|
|
40
|
-
```
|
|
41
|
-
|
|
41
|
+
```json
|
|
42
|
+
{
|
|
43
|
+
"$schema": "./node_modules/localarena/schema/localarena-evaluation-config-v1.schema.json",
|
|
44
|
+
"name": "Local smoke test",
|
|
45
|
+
"concurrency": 2,
|
|
46
|
+
"models": [
|
|
47
|
+
{
|
|
48
|
+
"name": "local-model",
|
|
49
|
+
"provider": "ollama",
|
|
50
|
+
"model": "qwen3:0.6b",
|
|
51
|
+
"parameters": {
|
|
52
|
+
"max_tokens": 64,
|
|
53
|
+
"temperature": 0
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
],
|
|
57
|
+
"tasks": [
|
|
58
|
+
{
|
|
59
|
+
"id": "arithmetic",
|
|
60
|
+
"prompt": "Return only the result of 6 multiplied by 7.",
|
|
61
|
+
"evaluator": {
|
|
62
|
+
"type": "numeric",
|
|
63
|
+
"expected": 42,
|
|
64
|
+
"tolerance": 0
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
"id": "structured",
|
|
69
|
+
"prompt": "Return only JSON with an answer field equal to 42.",
|
|
70
|
+
"evaluator": {
|
|
71
|
+
"type": "json",
|
|
72
|
+
"compare": true,
|
|
73
|
+
"expected": {
|
|
74
|
+
"answer": 42
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
]
|
|
79
|
+
}
|
|
80
|
+
```
|
|
42
81
|
|
|
43
|
-
|
|
82
|
+
Run every configured model against every configured task:
|
|
44
83
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
metadata={"prompt_id": "explain-rag"},
|
|
51
|
-
)
|
|
84
|
+
```bash
|
|
85
|
+
localarena run evaluation.json \
|
|
86
|
+
--output results.json \
|
|
87
|
+
--report report.html
|
|
88
|
+
```
|
|
52
89
|
|
|
53
|
-
|
|
54
|
-
|
|
90
|
+
The command prints progress as calls complete and returns a nonzero status if
|
|
91
|
+
any generation or scoring row failed. A failure stays in `results.json`; it
|
|
92
|
+
does not abort or silently shrink the matrix.
|
|
55
93
|
|
|
56
|
-
|
|
57
|
-
|
|
94
|
+
The complete multi-provider example is
|
|
95
|
+
[examples/evaluation-config.json](examples/evaluation-config.json).
|
|
58
96
|
|
|
59
|
-
|
|
97
|
+
## Providers
|
|
60
98
|
|
|
61
|
-
|
|
62
|
-
|
|
99
|
+
All built-in profiles use bounded, non-streaming Chat Completions requests and
|
|
100
|
+
model discovery through `/v1/models`.
|
|
63
101
|
|
|
64
|
-
|
|
102
|
+
| Profile | Default base URL | Default credential |
|
|
103
|
+
| --- | --- | --- |
|
|
104
|
+
| `llamacpp` | `http://127.0.0.1:8080/v1` | None |
|
|
105
|
+
| `ollama` | `http://127.0.0.1:11434/v1` | None |
|
|
106
|
+
| `lmstudio` | `http://127.0.0.1:1234/v1` | None |
|
|
107
|
+
| `openrouter` | `https://openrouter.ai/api/v1` | `OPENROUTER_API_KEY` |
|
|
108
|
+
| `openai` | `https://api.openai.com/v1` | `OPENAI_API_KEY` |
|
|
109
|
+
| `custom` | Required in config | Optional `api_key_env` |
|
|
65
110
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
111
|
+
A model entry may override `base_url`, select any model ID, set generation
|
|
112
|
+
parameters, and read credentials or sensitive headers from environment
|
|
113
|
+
variables. Literal API keys are rejected by the CLI configuration loader.
|
|
114
|
+
Hosted-provider keys are isolated: a standard cloud key is never forwarded
|
|
115
|
+
automatically when that profile is pointed at a different base URL.
|
|
69
116
|
|
|
70
|
-
|
|
71
|
-
console.log(standing.name, standing.rating.toFixed(2));
|
|
72
|
-
}
|
|
117
|
+
Useful commands:
|
|
73
118
|
|
|
74
|
-
|
|
119
|
+
```bash
|
|
120
|
+
localarena providers
|
|
121
|
+
localarena models ollama
|
|
122
|
+
localarena models custom --base-url http://127.0.0.1:9000/v1
|
|
123
|
+
localarena report results.json --output report.html
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
For an authenticated custom endpoint:
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
export LOCALARENA_CUSTOM_API_KEY="your-key"
|
|
130
|
+
localarena models custom \
|
|
131
|
+
--base-url https://inference.example.com/v1 \
|
|
132
|
+
--api-key-env LOCALARENA_CUSTOM_API_KEY
|
|
75
133
|
```
|
|
76
134
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
135
|
+
Requests make one attempt by default because replaying an ambiguous generation
|
|
136
|
+
can duplicate work or billing. Retries are explicit through
|
|
137
|
+
`policy.max_attempts`.
|
|
138
|
+
|
|
139
|
+
## Tasks and scoring
|
|
140
|
+
|
|
141
|
+
Every native task is provider-independent JSON-safe chat messages plus one
|
|
142
|
+
optional evaluator:
|
|
143
|
+
|
|
144
|
+
| Evaluator | Use |
|
|
145
|
+
| --- | --- |
|
|
146
|
+
| `exact` | Complete answer equality with explicit whitespace/case rules |
|
|
147
|
+
| `contains` | Require all or any configured strings |
|
|
148
|
+
| `regex` | Search or full-output format validation |
|
|
149
|
+
| `json` | Strict JSON validation or type-sensitive JSON comparison |
|
|
150
|
+
| `numeric` | Numeric answer with an absolute tolerance |
|
|
151
|
+
| `model_judge` | Live rubric scoring from another configured model |
|
|
152
|
+
| `null` | Generate and retain run metadata without a score |
|
|
153
|
+
|
|
154
|
+
Set `"judge_only": true` on a model entry to use that model for
|
|
155
|
+
`model_judge` tasks without evaluating or ranking it as a contestant.
|
|
156
|
+
|
|
157
|
+
`concurrency` bounds the number of in-flight rows. `repetitions` repeats the
|
|
158
|
+
entire grid. Each model keeps its own base URL, credential, model ID, and
|
|
159
|
+
generation settings, so local and hosted targets can run in the same
|
|
160
|
+
evaluation.
|
|
161
|
+
|
|
162
|
+
## Results and reports
|
|
163
|
+
|
|
164
|
+
`results.json` follows the versioned
|
|
165
|
+
[evaluation run schema](schema/localarena-evaluation-run-v1.schema.json). It
|
|
166
|
+
contains:
|
|
167
|
+
|
|
168
|
+
- one record for every model, task, and repetition;
|
|
169
|
+
- normalized status, timing, finish reason, token use, and attempt count;
|
|
170
|
+
- deterministic or live-judge scores;
|
|
171
|
+
- per-model score, pass rate, error, latency, token, and judge aggregates;
|
|
172
|
+
- Elo standings derived only from comparable successful task scores; and
|
|
173
|
+
- an arena snapshot whose match history can move between Python and
|
|
174
|
+
JavaScript.
|
|
175
|
+
|
|
176
|
+
JSON Schema validates the portable document shape. Load untrusted snapshots
|
|
177
|
+
with `run_from_dict` or `runFromSnapshot` as well; the SDK loaders enforce the
|
|
178
|
+
cross-field guarantee that every configured model × task × repetition has
|
|
179
|
+
exactly one row.
|
|
180
|
+
|
|
181
|
+
The HTML report is a single local file with no scripts, fonts, stylesheets, or
|
|
182
|
+
network requests. It includes a leaderboard, score bars, a model-by-task heat
|
|
183
|
+
map, and expandable row diagnostics.
|
|
184
|
+
|
|
185
|
+
Prompt text, evaluator criteria and reference answers, generated text, score
|
|
186
|
+
reasons, task metadata, and detailed errors are excluded from saved results
|
|
187
|
+
and reports by default. Non-secret evaluator type and live-judge target
|
|
188
|
+
provenance remain visible so scoring failures are attributable. Opt in only
|
|
189
|
+
when the content is safe to retain:
|
|
190
|
+
|
|
191
|
+
```bash
|
|
192
|
+
localarena run evaluation.json \
|
|
193
|
+
--include-content \
|
|
194
|
+
--output results-with-content.json \
|
|
195
|
+
--report report-with-content.html
|
|
196
|
+
```
|
|
81
197
|
|
|
82
|
-
|
|
198
|
+
Credentials, authorization headers, base URLs, and configured headers are
|
|
199
|
+
never serialized into an evaluation run.
|
|
83
200
|
|
|
84
|
-
|
|
85
|
-
Pair orientation flips every second round to reduce persistent left/right bias.
|
|
201
|
+
## Python API
|
|
86
202
|
|
|
87
203
|
```python
|
|
88
|
-
from localarena import
|
|
204
|
+
from localarena import (
|
|
205
|
+
EvaluationRunner,
|
|
206
|
+
ExactMatch,
|
|
207
|
+
ModelTarget,
|
|
208
|
+
PromptTask,
|
|
209
|
+
create_provider,
|
|
210
|
+
write_html_report,
|
|
211
|
+
)
|
|
89
212
|
|
|
90
|
-
|
|
91
|
-
|
|
213
|
+
provider = create_provider("ollama")
|
|
214
|
+
target = ModelTarget(
|
|
215
|
+
name="local-model",
|
|
216
|
+
provider=provider,
|
|
217
|
+
model="qwen3:0.6b",
|
|
218
|
+
max_tokens=64,
|
|
219
|
+
temperature=0,
|
|
220
|
+
)
|
|
221
|
+
task = PromptTask.from_text(
|
|
222
|
+
"capital",
|
|
223
|
+
"Reply with exactly Paris.",
|
|
224
|
+
evaluator=ExactMatch("Paris"),
|
|
225
|
+
)
|
|
92
226
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
227
|
+
run = EvaluationRunner([target], [task], max_concurrency=2).run(
|
|
228
|
+
name="Python API example"
|
|
229
|
+
)
|
|
230
|
+
print(run.to_json())
|
|
231
|
+
write_html_report(run, "report.html")
|
|
97
232
|
```
|
|
98
233
|
|
|
99
|
-
|
|
100
|
-
|
|
234
|
+
Provider construction does not perform network I/O. Calls occur only through
|
|
235
|
+
`list_models()`, `generate()`, or an evaluation runner.
|
|
101
236
|
|
|
102
|
-
|
|
103
|
-
const arena = new Arena(models);
|
|
237
|
+
## JavaScript API
|
|
104
238
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
239
|
+
```js
|
|
240
|
+
import {
|
|
241
|
+
EvaluationRunner,
|
|
242
|
+
ExactMatch,
|
|
243
|
+
ModelTarget,
|
|
244
|
+
PromptTask,
|
|
245
|
+
createProvider,
|
|
246
|
+
writeHTMLReport,
|
|
247
|
+
} from "localarena";
|
|
248
|
+
|
|
249
|
+
const target = new ModelTarget({
|
|
250
|
+
name: "local-model",
|
|
251
|
+
provider: createProvider("ollama"),
|
|
252
|
+
model: "qwen3:0.6b",
|
|
253
|
+
requestOverrides: { maxTokens: 64, temperature: 0 },
|
|
254
|
+
});
|
|
255
|
+
const task = PromptTask.fromText(
|
|
256
|
+
"capital",
|
|
257
|
+
"Reply with exactly Paris.",
|
|
258
|
+
{ evaluator: new ExactMatch("Paris") },
|
|
259
|
+
);
|
|
260
|
+
|
|
261
|
+
const run = await new EvaluationRunner([target], [task], {
|
|
262
|
+
maxConcurrency: 2,
|
|
263
|
+
}).run({ name: "JavaScript API example" });
|
|
264
|
+
|
|
265
|
+
console.log(run.toJSONString());
|
|
266
|
+
await writeHTMLReport(run, "report.html");
|
|
109
267
|
```
|
|
110
268
|
|
|
111
|
-
|
|
269
|
+
`AbortSignal` is supported for JavaScript provider calls and complete
|
|
270
|
+
evaluation runs.
|
|
112
271
|
|
|
113
|
-
|
|
114
|
-
|
|
272
|
+
Python's `arun()` stops scheduling new rows when its coroutine is cancelled.
|
|
273
|
+
A synchronous provider request already running in a worker thread continues
|
|
274
|
+
only until that provider call completes or reaches its configured timeout.
|
|
115
275
|
|
|
116
|
-
|
|
117
|
-
import json
|
|
118
|
-
from localarena import Arena
|
|
276
|
+
## Terminal and container benchmarks
|
|
119
277
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
278
|
+
A prompt-only completion is not an official terminal benchmark run. Tasks that
|
|
279
|
+
depend on a repository, shell, tools, container image, mutable state, time
|
|
280
|
+
limits, or a benchmark-specific grader must stay in their authoritative
|
|
281
|
+
harness. An adapter can import that harness result and reproducibility metadata
|
|
282
|
+
into the rating layer; LocalArena does not pretend that sending the task prompt
|
|
283
|
+
to a chat endpoint reproduces the environment.
|
|
123
284
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
const restored = Arena.fromJSON(payload);
|
|
127
|
-
```
|
|
285
|
+
See [provider and task support](docs/provider-and-task-support.md) for the
|
|
286
|
+
protocol rationale and external-adapter boundary.
|
|
128
287
|
|
|
129
|
-
|
|
288
|
+
## Elo core
|
|
130
289
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
"schema_version": 1,
|
|
134
|
-
"initial_rating": 1000,
|
|
135
|
-
"k_factor": 32,
|
|
136
|
-
"contestants": [
|
|
137
|
-
{"name": "a", "metadata": {}},
|
|
138
|
-
{"name": "b", "metadata": {}}
|
|
139
|
-
],
|
|
140
|
-
"matches": [
|
|
141
|
-
{
|
|
142
|
-
"id": 1,
|
|
143
|
-
"left": "a",
|
|
144
|
-
"right": "b",
|
|
145
|
-
"result": "left",
|
|
146
|
-
"metadata": {}
|
|
147
|
-
}
|
|
148
|
-
]
|
|
149
|
-
}
|
|
150
|
-
```
|
|
151
|
-
|
|
152
|
-
See [the JSON Schema](schema/localarena-v1.schema.json) for the serialized
|
|
153
|
-
contract. Restoring a snapshot replays the history instead of trusting stored
|
|
154
|
-
ratings or counters.
|
|
290
|
+
The original dependency-free arena remains available for human or external
|
|
291
|
+
verdicts:
|
|
155
292
|
|
|
156
|
-
|
|
293
|
+
```python
|
|
294
|
+
from localarena import Arena, Result
|
|
157
295
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
| `add(name, metadata)` | `add(name, metadata)` | Register a contestant |
|
|
162
|
-
| `rating(name)` | `rating(name)` | Read one current rating |
|
|
163
|
-
| `record(left, right, result, metadata)` | `record(left, right, result, metadata)` | Record one match |
|
|
164
|
-
| `standings()` / `leaderboard()` | `standings()` / `leaderboard()` | Return rating-sorted rows |
|
|
165
|
-
| `next_pair()` | `nextPair()` | Choose the least-played pair |
|
|
166
|
-
| `snapshot()` / `to_json()` | `snapshot()` / `JSON.stringify(arena)` | Export schema v1 |
|
|
167
|
-
| `Arena.from_snapshot()` / `from_json()` | `Arena.fromSnapshot()` / `fromJSON()` | Validate and replay |
|
|
168
|
-
| `expected_score(a, b)` | `expectedScore(a, b)` | Compute Elo expectation |
|
|
169
|
-
| `round_robin(names, rounds)` | `roundRobin(names, rounds)` | Build all-pairs schedule |
|
|
170
|
-
|
|
171
|
-
Results are `"left"`, `"right"`, or `"draw"`. Python also exports the
|
|
172
|
-
`Result` string enum; JavaScript exports a frozen `Result` object.
|
|
173
|
-
|
|
174
|
-
## Rating behavior
|
|
175
|
-
|
|
176
|
-
LocalArena uses the conventional Elo expectation:
|
|
177
|
-
|
|
178
|
-
```text
|
|
179
|
-
E(left) = 1 / (1 + 10 ^ ((right_rating - left_rating) / 400))
|
|
296
|
+
arena = Arena(["model-a", "model-b"])
|
|
297
|
+
arena.record("model-a", "model-b", Result.LEFT)
|
|
298
|
+
print(arena.standings())
|
|
180
299
|
```
|
|
181
300
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
normal floating-point precision.
|
|
185
|
-
|
|
186
|
-
Elo is an online rating system: changing match order can change the final
|
|
187
|
-
ratings. Keep and share the match log when reproducibility matters.
|
|
301
|
+
The JavaScript package exposes the same `Arena`, `Result`, `roundRobin`, and
|
|
302
|
+
schema-v1 snapshot contract.
|
|
188
303
|
|
|
189
|
-
## Development
|
|
304
|
+
## Development and release
|
|
190
305
|
|
|
191
306
|
```bash
|
|
192
|
-
# Python
|
|
193
307
|
python3 -m pip install -e python
|
|
194
308
|
python3 -m unittest discover -s python/tests
|
|
195
|
-
python3
|
|
196
|
-
python3 -m twine check python/dist/*
|
|
309
|
+
python3 scripts/check_runtime_parity.py
|
|
197
310
|
|
|
198
|
-
# JavaScript
|
|
199
311
|
npm ci
|
|
312
|
+
npm run check
|
|
200
313
|
npm test
|
|
201
|
-
npm audit --audit-level=low
|
|
202
314
|
npm pack --dry-run
|
|
203
315
|
```
|
|
204
316
|
|
|
205
|
-
CI
|
|
206
|
-
|
|
207
|
-
|
|
317
|
+
CI covers Python 3.10–3.13 and Node.js 18, 20, 22, and 24. A matching immutable
|
|
318
|
+
`v*` tag builds and verifies both distributions, then publishes the exact
|
|
319
|
+
artifacts to PyPI and npm.
|
|
208
320
|
|
|
209
321
|
## License
|
|
210
322
|
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
# Provider and task support
|
|
2
|
+
|
|
3
|
+
LocalArena evaluates outputs produced during the current run. It does not ship
|
|
4
|
+
recorded model answers, copy scores from public leaderboards, or substitute
|
|
5
|
+
cached responses when an endpoint is unavailable.
|
|
6
|
+
|
|
7
|
+
The rating engine remains offline and dependency-free. Provider calls are an
|
|
8
|
+
optional execution layer around that engine: generate two live answers, obtain
|
|
9
|
+
a verdict, and record `left`, `right`, or `draw` with provenance in match
|
|
10
|
+
metadata.
|
|
11
|
+
|
|
12
|
+
## Provider architecture
|
|
13
|
+
|
|
14
|
+
The first provider set uses one OpenAI-compatible Chat Completions transport
|
|
15
|
+
with five named presets. A preset supplies safe defaults such as the base URL
|
|
16
|
+
and expected authentication; the request, response, error, and model-listing
|
|
17
|
+
logic stays shared.
|
|
18
|
+
|
|
19
|
+
| Provider | Location | Default base URL | Chat and model endpoints | Authentication | Release behavior and notes | LocalArena adapter |
|
|
20
|
+
| --- | --- | --- | --- | --- | --- | --- |
|
|
21
|
+
| [llama.cpp server](https://github.com/ggml-org/llama.cpp/blob/master/tools/server/README.md) | Local or self-hosted | `http://127.0.0.1:8080/v1` | `POST /chat/completions`; `GET /models` | None by default; Bearer token when the server is started with `--api-key` | Bounded non-streaming requests; use a server alias for a stable model ID, and configure a compatible chat template | Named preset over the shared transport |
|
|
22
|
+
| [Ollama](https://docs.ollama.com/api/openai-compatibility) | Local | `http://127.0.0.1:11434/v1` | `POST /chat/completions`; `GET /models` | No server credential by default; some clients require a non-secret placeholder value, which Ollama ignores | Bounded non-streaming requests; models must be pulled before use | Named preset over the shared transport |
|
|
23
|
+
| [LM Studio](https://lmstudio.ai/docs/developer/openai-compat) | Local | `http://127.0.0.1:1234/v1` | `POST /chat/completions`; `GET /models` | None when authentication is disabled; Bearer token when [authentication is enabled](https://lmstudio.ai/docs/developer/core/authentication) | Bounded non-streaming requests; the selected model must be available to the local server | Named preset over the shared transport |
|
|
24
|
+
| [OpenRouter](https://openrouter.ai/docs/quickstart) | Hosted | `https://openrouter.ai/api/v1` | `POST /chat/completions`; [`GET /models`](https://openrouter.ai/docs/guides/overview/models) | Bearer API key | Bounded non-streaming requests; optional `HTTP-Referer` and `X-OpenRouter-Title` attribution headers | Named preset over the shared transport |
|
|
25
|
+
| [OpenAI](https://developers.openai.com/api/reference/resources/chat) | Hosted | `https://api.openai.com/v1` | `POST /chat/completions`; [`GET /models`](https://developers.openai.com/api/reference/resources/models) | Bearer API key; optional organization and project headers | Bounded non-streaming requests | Reference preset for the shared transport |
|
|
26
|
+
|
|
27
|
+
For all five presets, this release sends `stream: false`, reads the answer from
|
|
28
|
+
`choices[0].message.content`, and reads model IDs from `data[].id`. Responses
|
|
29
|
+
are buffered only up to the configured byte limit. The transport accepts an
|
|
30
|
+
explicit base URL, so another compatible local or hosted server can be used
|
|
31
|
+
without adding a new protocol implementation.
|
|
32
|
+
|
|
33
|
+
### Why one transport
|
|
34
|
+
|
|
35
|
+
All five servers deliberately expose the same small interoperability surface:
|
|
36
|
+
Chat Completions, model listing, Bearer-style headers when authentication is
|
|
37
|
+
needed, and the same core response envelope. Sharing that transport provides:
|
|
38
|
+
|
|
39
|
+
- the same core request fields and normalized result contract in the Python
|
|
40
|
+
and JavaScript packages;
|
|
41
|
+
- one place for timeouts, bounded response parsing, retries, response
|
|
42
|
+
validation, error normalization, and secret redaction;
|
|
43
|
+
- contract tests that apply to every preset;
|
|
44
|
+
- custom-base-URL support without a provider-specific dependency; and
|
|
45
|
+
- a small portable parameter set: `model`, `messages`, `max_tokens`,
|
|
46
|
+
`temperature`, `seed`, and stop sequences.
|
|
47
|
+
|
|
48
|
+
A compatibility layer is not a claim that every provider supports every
|
|
49
|
+
vendor-specific feature. Provider-only request fields remain explicit
|
|
50
|
+
pass-through options, and callers should use only fields supported by their
|
|
51
|
+
selected endpoint. A genuinely different wire protocol belongs in a separate
|
|
52
|
+
transport rather than in conditionals scattered through the shared one.
|
|
53
|
+
|
|
54
|
+
## Live execution and network guarantees
|
|
55
|
+
|
|
56
|
+
Provider-backed evaluation follows an explicit-network contract:
|
|
57
|
+
|
|
58
|
+
1. Importing LocalArena, constructing an arena or provider, scheduling pairs,
|
|
59
|
+
reading standings, and loading or saving snapshots perform no network I/O.
|
|
60
|
+
2. A network request occurs only when the caller explicitly asks to list
|
|
61
|
+
models, generate an answer, or run a provider-backed task.
|
|
62
|
+
3. Each request goes only to the configured base URL. There is no silent
|
|
63
|
+
provider fallback, cloud fallback, endpoint discovery, model download, or
|
|
64
|
+
model pull.
|
|
65
|
+
4. Local presets default to loopback addresses. Hosted presets must be chosen
|
|
66
|
+
explicitly and require their own credential.
|
|
67
|
+
5. Generation is live. An unreachable endpoint, missing model, invalid
|
|
68
|
+
response, or timeout is reported as an error; it is never replaced with a
|
|
69
|
+
bundled or previously recorded answer.
|
|
70
|
+
6. Generation makes one attempt by default. Retries are opt-in; when enabled,
|
|
71
|
+
they repeat the same configured request and remain visible in run metadata.
|
|
72
|
+
They do not switch models or providers.
|
|
73
|
+
|
|
74
|
+
Custom HTTP base URLs are useful for a trusted machine on a private network.
|
|
75
|
+
Hosted credentials should be sent only over HTTPS. The caller remains
|
|
76
|
+
responsible for trusting a custom endpoint and for deciding whether prompts may
|
|
77
|
+
leave the local machine.
|
|
78
|
+
|
|
79
|
+
## Secret handling
|
|
80
|
+
|
|
81
|
+
API keys are runtime configuration, not arena data.
|
|
82
|
+
|
|
83
|
+
- Keys may be supplied directly for the current process or read from a
|
|
84
|
+
provider-specific environment variable.
|
|
85
|
+
- A key is placed only in the authentication header for the configured
|
|
86
|
+
endpoint. It is not added to a URL, prompt, response, match metadata, or
|
|
87
|
+
snapshot.
|
|
88
|
+
- Request diagnostics and exceptions redact authentication and caller-supplied
|
|
89
|
+
secret headers.
|
|
90
|
+
- Result content retention defaults off. Prompts, evaluator criteria and
|
|
91
|
+
references, answer text, score reasons, and error details are retained only
|
|
92
|
+
when the caller explicitly opts in. Non-secret evaluator type and judge
|
|
93
|
+
target provenance remain available for auditability.
|
|
94
|
+
- Unknown custom exceptions are recorded with generic details so an endpoint
|
|
95
|
+
or credential embedded in an exception cannot leak into a result. Structured
|
|
96
|
+
provider failures may add sanitized status, retryability, and attempt counts.
|
|
97
|
+
- Local endpoints work without a real credential unless their operator has
|
|
98
|
+
enabled authentication.
|
|
99
|
+
- OpenAI and OpenRouter use separate credentials. A missing key fails before a
|
|
100
|
+
hosted request is sent.
|
|
101
|
+
- Snapshots can contain sanitized provenance such as provider name, model ID,
|
|
102
|
+
latency, token usage, finish reason, and a non-secret endpoint label. They
|
|
103
|
+
must never contain authorization headers or raw keys.
|
|
104
|
+
|
|
105
|
+
## Task taxonomy
|
|
106
|
+
|
|
107
|
+
The execution boundary is determined by what is needed to run and grade a task,
|
|
108
|
+
not by the benchmark's name.
|
|
109
|
+
|
|
110
|
+
| Task class | Examples | Execution path | Result recorded by LocalArena |
|
|
111
|
+
| --- | --- | --- | --- |
|
|
112
|
+
| Prompt preference | Two answers judged by a person or a live judge model | Native | `left`, `right`, or `draw`, plus prompt and run provenance |
|
|
113
|
+
| Prompt with a deterministic scorer | Exact or normalized match, structured-output validation, classification, extraction, or caller-supplied scoring code | Native | The scorer's comparison converted to `left`, `right`, or `draw` |
|
|
114
|
+
| Prompt with a rubric or reference | Free-form answer evaluated live against criteria or a reference answer | Native | The live evaluator's verdict and sanitized evidence |
|
|
115
|
+
| Stateful tool or simulator task | A task that needs browser state, an external simulator, a service topology, or another controlled runtime | External adapter | A normalized verdict imported from the task's authoritative runner |
|
|
116
|
+
| Containerized terminal benchmark | Repository repair, shell interaction, tests, filesystem mutation, environment reset, or image-based grading | Official environment and agent harness through an external adapter | The official harness outcome, task ID, and reproducibility metadata |
|
|
117
|
+
| Historical or third-party result | A result created outside the current run | Explicit import only | Marked as imported; never presented as a live LocalArena execution |
|
|
118
|
+
|
|
119
|
+
### Native prompt-scored tasks
|
|
120
|
+
|
|
121
|
+
A task runs natively when it can be represented by JSON-safe messages and its
|
|
122
|
+
score can be derived from returned content plus task-local criteria. LocalArena
|
|
123
|
+
can schedule the pair, call each configured provider, invoke the scorer or live
|
|
124
|
+
judge, and record the outcome without taking control of a shell or mutable
|
|
125
|
+
environment. A target marked `judge_only` can score other targets without
|
|
126
|
+
appearing as a contestant or receiving an arena rating.
|
|
127
|
+
|
|
128
|
+
Native task runs should preserve enough provenance to reproduce the comparison:
|
|
129
|
+
task and prompt identifiers or hashes, provider and model IDs, sampling
|
|
130
|
+
parameters, judge configuration, timestamps, latency, token usage when
|
|
131
|
+
reported, finish reason, and the scorer version. Secrets and sensitive prompt
|
|
132
|
+
content remain opt-in metadata and are excluded by default.
|
|
133
|
+
|
|
134
|
+
### External terminal and container tasks
|
|
135
|
+
|
|
136
|
+
A terminal benchmark is more than a prompt. Its result depends on the exact
|
|
137
|
+
repository state, container image, installed tools, shell policy, time limits,
|
|
138
|
+
network policy, environment reset, agent loop, and grader. Reimplementing only
|
|
139
|
+
its prompt through a chat endpoint would not reproduce the benchmark and must
|
|
140
|
+
not be described as an official run.
|
|
141
|
+
|
|
142
|
+
These benchmarks therefore stay under their published environment and agent
|
|
143
|
+
harness. A narrow external adapter starts or observes that harness and returns
|
|
144
|
+
a normalized run record to LocalArena. At minimum, that record should include:
|
|
145
|
+
|
|
146
|
+
- benchmark and harness version;
|
|
147
|
+
- task ID and attempt ID;
|
|
148
|
+
- contestant and model configuration;
|
|
149
|
+
- container image digest or equivalent environment identifier;
|
|
150
|
+
- seed, start and finish timestamps, status, and timeout state;
|
|
151
|
+
- authoritative score or pass/fail outcome;
|
|
152
|
+
- references to logs or grader evidence; and
|
|
153
|
+
- the mapping from that outcome to `left`, `right`, or `draw`.
|
|
154
|
+
|
|
155
|
+
The adapter boundary keeps container lifecycle, tool execution, credentials,
|
|
156
|
+
and benchmark-specific grading outside the dependency-free rating core. It also
|
|
157
|
+
allows an official harness to evolve independently while LocalArena continues
|
|
158
|
+
to schedule comparisons, retain provenance, and calculate standings.
|
|
159
|
+
|
|
160
|
+
## Support rule
|
|
161
|
+
|
|
162
|
+
Use the native path when the full task is “messages in, content out, score the
|
|
163
|
+
content.” Use an external adapter when correctness depends on state or tools
|
|
164
|
+
outside that exchange. In both paths, results come from an execution requested
|
|
165
|
+
by the caller; no precomputed leaderboard data is treated as a live run.
|