llm-relay 0.15.3 → 0.16.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 +150 -85
- package/config.example.json +27 -41
- package/dist/benchmarks.d.ts +96 -19
- package/dist/benchmarks.js +168 -27
- package/dist/benchmarks.js.map +1 -1
- package/dist/candidates.d.ts +23 -10
- package/dist/candidates.js +80 -21
- package/dist/candidates.js.map +1 -1
- package/dist/catalog.d.ts +12 -0
- package/dist/catalog.js +47 -2
- package/dist/catalog.js.map +1 -1
- package/dist/circuit-breaker.d.ts +1 -1
- package/dist/circuit-breaker.js +1 -1
- package/dist/cli.d.ts +6 -2
- package/dist/cli.js +244 -187
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +18 -1
- package/dist/config.js +35 -5
- package/dist/config.js.map +1 -1
- package/dist/dispatch.d.ts +9 -1
- package/dist/dispatch.js +7 -0
- package/dist/dispatch.js.map +1 -1
- package/dist/dynamic-pools.d.ts +20 -3
- package/dist/dynamic-pools.js +98 -38
- package/dist/dynamic-pools.js.map +1 -1
- package/dist/kernel/contracts.d.ts +14 -180
- package/dist/kernel/contracts.js +15 -5
- package/dist/kernel/contracts.js.map +1 -1
- package/dist/kernel/index.d.ts +0 -1
- package/dist/kernel/index.js +0 -1
- package/dist/kernel/index.js.map +1 -1
- package/dist/kernel/request-lifecycle.d.ts +1 -38
- package/dist/kernel/request-lifecycle.js +0 -90
- package/dist/kernel/request-lifecycle.js.map +1 -1
- package/dist/metadata.d.ts +32 -1
- package/dist/metadata.js +48 -30
- package/dist/metadata.js.map +1 -1
- package/dist/offload.js +2 -1
- package/dist/offload.js.map +1 -1
- package/dist/ping/cadence.d.ts +6 -1
- package/dist/ping/cadence.js +87 -9
- package/dist/ping/cadence.js.map +1 -1
- package/dist/ping/probe-cache.d.ts +5 -0
- package/dist/ping/probe-cache.js +56 -3
- package/dist/ping/probe-cache.js.map +1 -1
- package/dist/ping/runtime-telemetry.d.ts +10 -0
- package/dist/ping/runtime-telemetry.js +55 -5
- package/dist/ping/runtime-telemetry.js.map +1 -1
- package/dist/pool-health.d.ts +7 -1
- package/dist/pool-health.js +21 -9
- package/dist/pool-health.js.map +1 -1
- package/dist/registry.d.ts +1 -1
- package/dist/registry.js +3 -3
- package/dist/registry.js.map +1 -1
- package/dist/routes/admin.js +16 -2
- package/dist/routes/admin.js.map +1 -1
- package/dist/server.d.ts +2 -2
- package/dist/server.js +111 -38
- package/dist/server.js.map +1 -1
- package/dist/tier-data.d.ts +29 -3
- package/dist/tier-data.js +26 -12
- package/dist/tier-data.js.map +1 -1
- package/docs/tier-data.json +24842 -8365
- package/package.json +4 -1
- package/scripts/install-skill.mjs +2 -2
- package/scripts/sync-tiers.mjs +326 -0
- package/scripts/tier-scoring.d.mts +20 -0
- package/scripts/tier-scoring.mjs +327 -0
- package/skills/llm-relay/SKILL.md +59 -24
- package/dist/kernel/tier-snapshot.d.ts +0 -48
- package/dist/kernel/tier-snapshot.js +0 -126
- package/dist/kernel/tier-snapshot.js.map +0 -1
package/README.md
CHANGED
|
@@ -22,32 +22,46 @@ Runs the compiled CLI as a real process against a local flaky-model backend + st
|
|
|
22
22
|
|
|
23
23
|
## Quick Start & Free Model Onboarding
|
|
24
24
|
|
|
25
|
-
`llm-relay` comes pre-configured with **100%-free model presets** (NVIDIA NIM, Groq, Gemini Free, OpenRouter Free, Cerebras, SambaNova) and supports **pooling your existing subscriptions** (ChatGPT / OpenAI API, AGY, Anthropic).
|
|
25
|
+
`llm-relay` comes pre-configured with **100%-free model presets** (NVIDIA NIM, Groq, Gemini Free, OpenRouter Free, Cerebras, SambaNova) and supports **pooling your existing subscriptions** (ChatGPT / OpenAI API, AGY, Anthropic). This section is the whole install; the staged, hand-it-to-an-AI version with more depth is [docs/QUICKSTART.md](docs/QUICKSTART.md).
|
|
26
26
|
|
|
27
|
-
### Step 1:
|
|
27
|
+
### Step 1: Get keys
|
|
28
28
|
```bash
|
|
29
29
|
npx llm-relay onboard
|
|
30
30
|
```
|
|
31
|
-
|
|
31
|
+
Creates `~/.llm-relay/config.json` (first run), scans your environment for keys you already have, and walks the free providers with direct signup links. **The part only you can do:** open the links, create the accounts, and paste each API key when prompted — onboarding saves them to `~/.llm-relay/.env`. One or two providers is enough to start; you can rerun `onboard` any time to add more. (A key added *while the relay is running* is picked up on the next relay restart, not instantly.)
|
|
32
32
|
|
|
33
|
-
### Step 2:
|
|
33
|
+
### Step 2: Point your client at the relay
|
|
34
34
|
|
|
35
35
|
**For Claude Desktop:**
|
|
36
36
|
```bash
|
|
37
37
|
llm-relay setup claude-desktop
|
|
38
38
|
```
|
|
39
|
-
Auto-patches `%APPDATA%\Claude\claude_desktop_config.json` (Windows) or `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) to route Claude Desktop through `llm-relay` (`http://127.0.0.1:8791`).
|
|
39
|
+
Auto-patches `%APPDATA%\Claude\claude_desktop_config.json` (Windows) or `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) to route Claude Desktop through `llm-relay` (`http://127.0.0.1:8791`), with an isolated `CLAUDE_CONFIG_DIR` so it never conflicts with your claude.ai login. **Restart Claude Desktop afterwards** — it reads this file only at launch.
|
|
40
40
|
|
|
41
41
|
**For Claude CLI (`claude`):**
|
|
42
42
|
```bash
|
|
43
43
|
llm-relay setup claude-cli
|
|
44
44
|
```
|
|
45
|
-
|
|
45
|
+
Prints the two ready-made wrapper scripts (`claude-proxied.ps1` / `claude-proxied.sh`) and what they set. Nothing is modified: run `claude` through a wrapper when you want the relay in the path — see [Use it from your projects](#use-it-from-your-projects) for the wrapper vs inline-env choice.
|
|
46
46
|
|
|
47
|
-
### Step 3: Start
|
|
47
|
+
### Step 3: Start it and prove it works
|
|
48
48
|
```bash
|
|
49
|
-
llm-relay
|
|
49
|
+
llm-relay # starts the proxy on 127.0.0.1:8791 (leave it running)
|
|
50
|
+
llm-relay keys # are the credentials good?
|
|
51
|
+
llm-relay pools --probe # does each configured model actually answer?
|
|
50
52
|
```
|
|
53
|
+
`keys` and `pools --probe` are the two checks worth running before blaming anything else — see [Verifying a setup](#verifying-a-setup--two-checks-two-different-questions) for what each can and cannot prove.
|
|
54
|
+
|
|
55
|
+
### Step 4: Keep it running
|
|
56
|
+
|
|
57
|
+
The relay is a foreground process; if you close the terminal, everything you pointed at it stops working. Have your OS start it at login:
|
|
58
|
+
|
|
59
|
+
- **Windows** — save this as `llm-relay.vbs` in `shell:startup` (Win+R → `shell:startup`):
|
|
60
|
+
```vbs
|
|
61
|
+
CreateObject("WScript.Shell").Run "llm-relay", 0
|
|
62
|
+
```
|
|
63
|
+
- **macOS** — `brew services` has no formula for this; use a LaunchAgent: save as `~/Library/LaunchAgents/com.llm-relay.plist` with `ProgramArguments` = the full path from `which llm-relay`, `RunAtLoad` = true, then `launchctl load` it.
|
|
64
|
+
- **Linux** — a user systemd unit: `systemd-run --user --unit=llm-relay $(which llm-relay)` to try it, or write `~/.config/systemd/user/llm-relay.service` and `systemctl --user enable --now llm-relay`.
|
|
51
65
|
|
|
52
66
|
---
|
|
53
67
|
|
|
@@ -56,7 +70,7 @@ llm-relay
|
|
|
56
70
|
### 1. 100%-Free Presets & Subscription Pooling
|
|
57
71
|
- **100%-Free Tier**: NVIDIA NIM (`build.nvidia.com`), Groq (`console.groq.com/keys`), Gemini Free (`aistudio.google.com/app/apikey`), OpenRouter Free (`openrouter.ai/keys`), Cerebras, SambaNova.
|
|
58
72
|
- **Subscription Tier**: Mapped as `subscription` in config (e.g. OpenAI `OPENAI_API_KEY`, Anthropic `ANTHROPIC_API_KEY`).
|
|
59
|
-
- **Priority Cascade**:
|
|
73
|
+
- **Priority Cascade**: within a multi-candidate route (an array or a pool), higher-ranked targets are tried first with automatic failover to the rest on 429s. (This is in-route failover — it does not reroute traffic between clients or tiers by itself; that is the opt-in offload switch below.)
|
|
60
74
|
|
|
61
75
|
### 2. Stability-Aware Dynamic Routing & Auto-Failover
|
|
62
76
|
- `CircuitBreaker` tracks latency, jitter, spike rates, and remaining rate-limit quota headers (`x-ratelimit-remaining`), computing a live **Stability Score (0–100)** for every provider target.
|
|
@@ -73,7 +87,8 @@ llm-relay
|
|
|
73
87
|
|
|
74
88
|
### 4. Background Adaptive Health Monitoring & Persistent Caching
|
|
75
89
|
- **Adaptive Cadence Loop**: Background `PingLoop` dynamically adjusts probe frequency across 4 operational modes: `speed` (2s interval at startup/activity), `normal` (10s), `slow` (30s after 5m idle), and `forced` (4s).
|
|
76
|
-
- **
|
|
90
|
+
- **Selective probes**: The background loop probes only deployments present in materialized routing, with pool leaders first. A recent successful real request satisfies freshness; broken targets retry with exponential backoff instead of being hammered every tick. Explicit `llm-relay ping` remains a full-catalog diagnostic.
|
|
91
|
+
- **Persistent State**: Background probes, real-world proxy calls, dynamic catalogs, and local keys persist under `~/.llm-relay/` (`models-cache.json`, `probe-cache.json`, `runtime-telemetry.json`, `.env`). JSON caches use bounded write-behind and flush during graceful shutdown, keeping whole-file rewrites out of request/probe hot paths.
|
|
77
92
|
|
|
78
93
|
### 5. Document (PDF/Office) Attachments on Non-Anthropic Backends
|
|
79
94
|
- Anthropic `document` content blocks are converted to markdown **before** the request reaches an
|
|
@@ -99,21 +114,23 @@ llm-relay
|
|
|
99
114
|
|
|
100
115
|
| Command | Description |
|
|
101
116
|
| :--- | :--- |
|
|
102
|
-
| `llm-relay` | Start
|
|
103
|
-
| `llm-relay onboard` |
|
|
104
|
-
| `llm-relay setup
|
|
105
|
-
| `llm-relay
|
|
106
|
-
| `llm-relay
|
|
107
|
-
| `llm-relay pools [
|
|
108
|
-
| `llm-relay
|
|
109
|
-
| `llm-relay
|
|
110
|
-
| `llm-relay
|
|
111
|
-
| `llm-relay
|
|
112
|
-
| `llm-relay
|
|
113
|
-
| `llm-relay
|
|
114
|
-
| `llm-relay offload
|
|
115
|
-
| `llm-relay candidates [-p <name>]` |
|
|
116
|
-
| `llm-relay dispatch [lane] [
|
|
117
|
+
| `llm-relay` | Start proxy |
|
|
118
|
+
| `llm-relay onboard` | Set up provider keys |
|
|
119
|
+
| `llm-relay setup [target]` | `target`: `claude-cli` | `claude-desktop` |
|
|
120
|
+
| `llm-relay keys | check-keys` | Check provider keys |
|
|
121
|
+
| `llm-relay pools [--probe]` | List pool members; `--probe` tests each |
|
|
122
|
+
| `llm-relay pools <action> <name> [<spec>...]` | `action`: `set` | `add` | `remove` | `delete` |
|
|
123
|
+
| `llm-relay routing <action> ...` | `action`: `show` | `get` | `default` | `tier` | `subagent` | `sort` | `benchmark` | `set` | `unset` |
|
|
124
|
+
| `llm-relay config <action> [<path>] [<value>]` | `action`: `show` | `get` | `set` | `unset` |
|
|
125
|
+
| `llm-relay telemetry` | Print telemetry/quota JSON |
|
|
126
|
+
| `llm-relay models [-p <name>] [-r]` | List provider models |
|
|
127
|
+
| `llm-relay ping [-p <name>]` | Probe providers |
|
|
128
|
+
| `llm-relay offload [status]` | Show aggregate offload state |
|
|
129
|
+
| `llm-relay offload <harness> <on\|off> [--scope <scope>]` | Set one harness's rule |
|
|
130
|
+
| `llm-relay candidates [-p <name>]` | Show offload target data |
|
|
131
|
+
| `llm-relay dispatch [lane] [options]` | Choose next dispatch lane |
|
|
132
|
+
| `llm-relay help \| --help \| -h` | Full flag/endpoint reference |
|
|
133
|
+
| `llm-relay version \| --version \| -v` | Print version |
|
|
117
134
|
|
|
118
135
|
---
|
|
119
136
|
|
|
@@ -124,25 +141,25 @@ and validate the complete result before writing it. Restart a running proxy afte
|
|
|
124
141
|
|
|
125
142
|
```bash
|
|
126
143
|
# Static pool: members are tried/ranked according to the normal pool rules.
|
|
127
|
-
llm-relay pools set
|
|
128
|
-
llm-relay pools add
|
|
129
|
-
llm-relay pools remove
|
|
130
|
-
llm-relay pools delete
|
|
144
|
+
llm-relay pools set medium nim/z-ai/glm-5.2 openrouter/openai/gpt-5.2-codex
|
|
145
|
+
llm-relay pools add medium gemini/gemini-2.5-flash
|
|
146
|
+
llm-relay pools remove medium gemini/gemini-2.5-flash
|
|
147
|
+
llm-relay pools delete medium
|
|
131
148
|
|
|
132
|
-
# Dynamic pool:
|
|
133
|
-
llm-relay pools set
|
|
149
|
+
# Dynamic effort pool: an empty configured prefix, then evidence-ranked free models.
|
|
150
|
+
llm-relay pools set medium --free --effort medium
|
|
134
151
|
|
|
135
152
|
# Main fallback, Claude tier maps, subagent destinations, and ranking.
|
|
136
153
|
llm-relay routing default nim/z-ai/glm-5.2 openrouter/openai/gpt-5.2-codex
|
|
137
|
-
llm-relay routing tier sonnet pool/
|
|
138
|
-
llm-relay routing subagent default pool/
|
|
154
|
+
llm-relay routing tier sonnet pool/high
|
|
155
|
+
llm-relay routing subagent default pool/medium
|
|
139
156
|
llm-relay routing sort off
|
|
140
157
|
llm-relay routing tier opus --clear
|
|
141
158
|
|
|
142
159
|
# Inspect or change any less-common routing field using a JSON value.
|
|
143
160
|
llm-relay routing show
|
|
144
161
|
llm-relay config get routing.pools
|
|
145
|
-
llm-relay config set routing.ladder '[{"id":"
|
|
162
|
+
llm-relay config set routing.ladder '[{"id":"medium","kind":"relay","spec":"pool/medium"}]'
|
|
146
163
|
llm-relay config unset routing.ladder
|
|
147
164
|
```
|
|
148
165
|
|
|
@@ -315,17 +332,18 @@ a `routing` block that maps each request's `model` to one provider + backend mod
|
|
|
315
332
|
"gemini": { "base": "https://generativelanguage.googleapis.com/v1beta/openai", "kind": "openai", "authEnv": "GEMINI_API_KEY" }
|
|
316
333
|
},
|
|
317
334
|
"routing": {
|
|
318
|
-
|
|
319
|
-
// (it only sorts when there is more than one) AND failover. A lone pinned model disables both.
|
|
320
|
-
"default": ["nim/z-ai/glm-5.2", "nim/deepseek-ai/deepseek-v4-pro"],
|
|
335
|
+
"default": "pool/medium",
|
|
321
336
|
"tiers": { // Claude tier (substring match) → provider/model
|
|
322
|
-
"opus":
|
|
323
|
-
"
|
|
324
|
-
"
|
|
325
|
-
"
|
|
337
|
+
"opus": "pool/xhigh",
|
|
338
|
+
"fable": "pool/xhigh",
|
|
339
|
+
"sonnet": "pool/high",
|
|
340
|
+
"haiku": "pool/medium"
|
|
326
341
|
},
|
|
327
342
|
"pools": { // addressable as model "pool/<name>"
|
|
328
|
-
"
|
|
343
|
+
"low": { "preferred": [], "include": "free", "effort": "low" },
|
|
344
|
+
"medium": { "preferred": [], "include": "free", "effort": "medium" },
|
|
345
|
+
"high": { "preferred": [], "include": "free", "effort": "high" },
|
|
346
|
+
"xhigh": { "preferred": [], "include": "free", "effort": "xhigh" }
|
|
329
347
|
}
|
|
330
348
|
},
|
|
331
349
|
"mode": "repair", // detect | repair (strict accepted, aliases detect)
|
|
@@ -340,7 +358,7 @@ a `routing` block that maps each request's `model` to one provider + backend mod
|
|
|
340
358
|
|
|
341
359
|
**Routing (lifted from free-claude-code's proven scheme — split on the first `/` only):**
|
|
342
360
|
1. **Pool** — a request `model` of `pool/<name>` expands to that pool's whole candidate list,
|
|
343
|
-
which is then
|
|
361
|
+
which is then fitness-ranked and failed over. Use this to ask for *the best available*
|
|
344
362
|
model instead of naming one. An unknown pool is a **400, never a silent fallback** to the
|
|
345
363
|
default — a typo must not quietly succeed against a different model.
|
|
346
364
|
2. **Namespaced** — a request `model` of `provider/rest` where `provider` is a configured
|
|
@@ -360,18 +378,28 @@ reserved provider name; configuring a provider called `pool` fails at load.
|
|
|
360
378
|
Pools can be static arrays, or automatic free-model pools:
|
|
361
379
|
|
|
362
380
|
```jsonc
|
|
363
|
-
"
|
|
364
|
-
"preferred": [
|
|
365
|
-
"include": "free"
|
|
381
|
+
"medium": {
|
|
382
|
+
"preferred": [],
|
|
383
|
+
"include": "free",
|
|
384
|
+
"effort": "medium"
|
|
366
385
|
}
|
|
367
386
|
```
|
|
368
387
|
|
|
369
|
-
The
|
|
388
|
+
The configured prefix remains first in exactly the written order. The relay then appends every
|
|
370
389
|
model discovered from a `tierType: "free"` provider (excluding a model when its catalog publishes
|
|
371
390
|
a positive price), plus zero-priced or explicitly free-labelled models from `tierType: "mixed"`
|
|
372
|
-
providers
|
|
391
|
+
providers. `effort` may be `low`, `medium`, `high`, or `xhigh`. These are cumulative raw-capability
|
|
392
|
+
floors (50/60/70/80), not ceilings. Admission compares a whole-point capability score; an existing
|
|
393
|
+
member remains until it falls two points below its floor, preventing refresh noise from flapping the
|
|
394
|
+
pool. Automatic membership also requires an exact SKU match and at least three published capability
|
|
395
|
+
or task-fit signals; confidence, stability, and metadata affect ordering, not eligibility.
|
|
396
|
+
A strong free model remains eligible for `low`, while higher effort narrows upward
|
|
397
|
+
(`xhigh ⊆ high ⊆ medium ⊆ low`). Exact SKUs known not to support tools are excluded.
|
|
373
398
|
Catalog refreshes re-materialize the pool automatically; adding new free models never requires a
|
|
374
|
-
config edit.
|
|
399
|
+
config edit. Materialization builds and ranks one common discovered roster, then filters that
|
|
400
|
+
snapshot into all effort pools; the result is reused for a 30-second ranking epoch and invalidated
|
|
401
|
+
immediately by a catalog revision. Legacy array pools keep their existing whole-array
|
|
402
|
+
`benchmarkSort` behaviour, with their ranking likewise reused within a short epoch.
|
|
375
403
|
|
|
376
404
|
**What failover actually does** (both `/v1/messages` and `/v1/chat/completions`):
|
|
377
405
|
|
|
@@ -453,9 +481,12 @@ to marked subagents only (the current behavior) or to the whole conversation:
|
|
|
453
481
|
```jsonc
|
|
454
482
|
"routing": {
|
|
455
483
|
"tiers": { "opus": "anthropic", "sonnet": "anthropic", "haiku": "anthropic", "fable": "anthropic" },
|
|
456
|
-
"subagents": {
|
|
484
|
+
"subagents": {
|
|
485
|
+
"opus": "pool/xhigh", "fable": "pool/xhigh",
|
|
486
|
+
"sonnet": "pool/high", "haiku": "pool/medium", "default": "pool/medium"
|
|
487
|
+
},
|
|
457
488
|
"offload": {
|
|
458
|
-
"claude": { "enabled": true, "scope": "subagents" },
|
|
489
|
+
"claude": { "enabled": true, "scope": "subagents", "freeOnly": true },
|
|
459
490
|
"codex": { "enabled": false, "scope": "all" }
|
|
460
491
|
}
|
|
461
492
|
}
|
|
@@ -466,17 +497,26 @@ to marked subagents only (the current behavior) or to the whole conversation:
|
|
|
466
497
|
Claude or Codex quota is exhausted. Rules may use any future client name; an explicit `default`
|
|
467
498
|
rule is the opt-in catch-all for otherwise unnamed front doors. All rules are off by default.
|
|
468
499
|
|
|
469
|
-
|
|
500
|
+
`"freeOnly": true` is the money guard: this client's rerouted traffic may only reach deployments
|
|
501
|
+
assessed **free** (published zero price, an explicitly `:free`-labelled id, or a `tierType: "free"`
|
|
502
|
+
provider). Anything else — including *unknown* cost, and the Anthropic passthrough — is filtered
|
|
503
|
+
out after pool expansion, and if nothing free remains the request is **refused with a clean 503**
|
|
504
|
+
naming the rule, never silently sent somewhere that bills. It also binds per-call `@relay:`
|
|
505
|
+
directives, so a subagent prompt cannot spend money past it. Set it in the config file or with
|
|
506
|
+
`llm-relay config set routing.offload.claude.freeOnly true` (restart applies it).
|
|
507
|
+
|
|
508
|
+
The CLI changes one harness without restarting the proxy:
|
|
470
509
|
|
|
471
510
|
```bash
|
|
472
511
|
llm-relay offload status
|
|
473
|
-
llm-relay offload
|
|
474
|
-
llm-relay offload codex on --scope all
|
|
475
|
-
llm-relay offload claude off
|
|
512
|
+
llm-relay offload <harness> <on|off> [--scope <scope>]
|
|
476
513
|
```
|
|
477
514
|
|
|
478
|
-
|
|
479
|
-
|
|
515
|
+
`<harness>` is `claude`, `codex`, or another configured client. `<scope>` is `subagents` or
|
|
516
|
+
`all` (default: `subagents`).
|
|
517
|
+
|
|
518
|
+
The legacy boolean form remains supported in config files as a global subagents-only rule
|
|
519
|
+
(`"offload": false`). The CLI requires a harness name for changes. `GET /offload?client=claude` reads one rule;
|
|
480
520
|
`POST /offload` accepts `{"client":"claude","enabled":true,"scope":"all"}`. Changes are
|
|
481
521
|
persisted and take effect on the next request.
|
|
482
522
|
|
|
@@ -533,7 +573,7 @@ description = "Read-only coding child routed through llm-relay."
|
|
|
533
573
|
developer_instructions = "Work read-only. Return a concise result to the parent and do not modify files."
|
|
534
574
|
|
|
535
575
|
model_provider = "llm-relay"
|
|
536
|
-
model = "pool/
|
|
576
|
+
model = "pool/medium"
|
|
537
577
|
model_reasoning_effort = "medium"
|
|
538
578
|
```
|
|
539
579
|
|
|
@@ -546,12 +586,12 @@ description = "General-purpose read-only child routed through llm-relay."
|
|
|
546
586
|
developer_instructions = "Work read-only. Return a concise result to the parent and do not modify files."
|
|
547
587
|
|
|
548
588
|
model_provider = "llm-relay"
|
|
549
|
-
model = "pool/
|
|
589
|
+
model = "pool/medium"
|
|
550
590
|
model_reasoning_effort = "medium"
|
|
551
591
|
```
|
|
552
592
|
|
|
553
593
|
With that override, a normal “use a subagent” request keeps the parent native while the generic child
|
|
554
|
-
goes through `pool/
|
|
594
|
+
goes through `pool/medium`; named agents can still select a different pool explicitly.
|
|
555
595
|
|
|
556
596
|
Run Codex normally, without the `llm-relay` profile. Ask the parent to use exactly one subagent of
|
|
557
597
|
type `relay_coding`; Codex keeps the parent on its normal provider and starts the child through the
|
|
@@ -560,11 +600,11 @@ relay. The relay pool then chooses the configured provider and can fail over nor
|
|
|
560
600
|
Enable only Codex child offload in `~/.llm-relay/config.json`:
|
|
561
601
|
|
|
562
602
|
```bash
|
|
563
|
-
llm-relay offload
|
|
603
|
+
llm-relay offload <harness> on --scope <scope>
|
|
564
604
|
```
|
|
565
605
|
|
|
566
|
-
|
|
567
|
-
|
|
606
|
+
For Codex, use `harness=codex` with `scope=subagents`; use `scope=all` to include the parent
|
|
607
|
+
conversation. Claude's rule is unaffected.
|
|
568
608
|
|
|
569
609
|
The `llm-relay` profile remains available as an explicit all-relay mode, but it routes the parent
|
|
570
610
|
through the relay too and is not the split setup described above. The automatic
|
|
@@ -576,18 +616,23 @@ This applies to local Codex clients that can reach `127.0.0.1`. Hosted ChatGPT/C
|
|
|
576
616
|
reach a loopback relay, and the relay cannot spend a ChatGPT subscription on behalf of an upstream
|
|
577
617
|
request; those remain separate CLI/client-bound dispatch lanes.
|
|
578
618
|
|
|
579
|
-
Whole-task CLI dispatch can likewise vary by tier with `routing.ladders.{
|
|
580
|
-
Use `llm-relay dispatch --tier
|
|
581
|
-
`subagents.default` is selected (normally `
|
|
619
|
+
Whole-task CLI dispatch can likewise vary by tier with `routing.ladders.{low,medium,high,xhigh}`.
|
|
620
|
+
Use `llm-relay dispatch --tier high -t "..."`; without `--tier`, the ladder matching
|
|
621
|
+
`subagents.default` is selected (normally `medium`). The legacy single `routing.ladder` remains
|
|
582
622
|
supported for configurations that do not need tier-specific CLI models.
|
|
583
623
|
|
|
624
|
+
When a lane turns out to be spent, say **which way**: `llm-relay dispatch -x <lane> --outcome
|
|
625
|
+
rate_limited` cools it briefly (15 min default — rate limits reset on a fast clock) while
|
|
626
|
+
`--outcome quota_exhausted` cools it for an hour; `--retry-after-ms <n>` passes the vendor's own
|
|
627
|
+
stated reset and beats both defaults. A plain `-x <lane>` keeps its old 15-minute behaviour.
|
|
628
|
+
|
|
584
629
|
### Choosing where to offload (`llm-relay candidates`)
|
|
585
630
|
|
|
586
631
|
```
|
|
587
|
-
target pools / tiers
|
|
588
|
-
|
|
589
|
-
nim/
|
|
590
|
-
nim/
|
|
632
|
+
target pools / tiers fit raw cap agentic coding BFCL arena $/Mout verdict p95 quota breaker ctx
|
|
633
|
+
ollama-cloud/kimi-k3 low,medium,high... 79.9 96.6 87.3/4 50.1 76.2 - - - Pending - - closed 1049k~
|
|
634
|
+
nim/z-ai/glm-5.2 low,medium,high... 71.8 83.3 76.6/4 43.1 68.8 - - $2.402~ Pending - - closed 1049k~
|
|
635
|
+
nim/deepseek-ai/deepseek-v4-pro low,medium,@haiku 64.9 67.4 67.4/5 36.4 59.4 - 1457 $0.87~ Pending - - closed 1049k~
|
|
591
636
|
```
|
|
592
637
|
|
|
593
638
|
Every offload target with its dimensions side by side: capability from each leaderboard separately,
|
|
@@ -609,15 +654,34 @@ They **disagree** — the agentic index puts deepseek above kimi while the codin
|
|
|
609
654
|
above deepseek — which is exactly why each keeps its own column, and why a blank cell means *not
|
|
610
655
|
measured*, never *bad*.
|
|
611
656
|
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
657
|
+
The raw dimensions remain separate — capability, latency and remaining quota answer different
|
|
658
|
+
questions. Pool ordering uses three explicit derived scores:
|
|
659
|
+
|
|
660
|
+
- `raw` is fixed at 40% agentic/tool use, 35% coding, and 25% general reasoning. Each source is
|
|
661
|
+
mapped through persisted raw-value calibration anchors, so an unrelated leaderboard addition
|
|
662
|
+
cannot silently move every model. If an entire dimension is missing, it is estimated by ridge
|
|
663
|
+
regression from models with overlapping dimensions rather than disappearing from the denominator.
|
|
664
|
+
Artificial Analysis Agentic and BFCL Overall feed agentic capability; AA Coding and Aider pass
|
|
665
|
+
rate feed coding; AA Intelligence and LMArena feed general reasoning.
|
|
666
|
+
- Design Arena's differently covered specialist categories, BFCL irrelevance, and Aider formatting
|
|
667
|
+
compliance are task-fit signals, not raw capability. This prevents a model measured on a favorable
|
|
668
|
+
specialized subset from gaining an effort tier.
|
|
669
|
+
- `cap` is `raw` shrunk toward neutral by capability evidence confidence. Direct dimension coverage,
|
|
670
|
+
published capability signals, and imputation quality determine confidence; fuzzy model-name
|
|
671
|
+
matches get half confidence. It affects ordering, never effort eligibility. `/4c5p` means four
|
|
672
|
+
direct capability signals and five total publications; `neut` means no capability evidence.
|
|
673
|
+
Operational telemetry never substitutes for capability.
|
|
674
|
+
- `fit` is 75% `cap`, 20% deployment operations, and 5% task-fit metadata. Operations combine
|
|
675
|
+
synthetic probe stability with success/speed/recency from at least five real calls. Metadata
|
|
676
|
+
uses the separate specialist/behavior score, exact-SKU tool support, and provider/reference
|
|
677
|
+
context and output limits. Missing inputs are neutral (50), not zero. A known tool-incompatible
|
|
678
|
+
SKU is excluded from automatic effort pools; breaker-open and credential-faulted deployments are
|
|
679
|
+
demoted after scoring.
|
|
680
|
+
|
|
681
|
+
Only coarse `raw` capability plus the exact-match/three-publication gate decides whether a model
|
|
682
|
+
clears an effort floor. The generated snapshot persists the two-point exit band. `fit` decides the
|
|
683
|
+
order among eligible deployments. The JSON view exposes dimensions, direct/imputed coverage, task
|
|
684
|
+
fit, and confidence.
|
|
621
685
|
|
|
622
686
|
**Limits and prices are per-(provider, model), and labelled.** The same model id on two providers is
|
|
623
687
|
two deployments — different context ceilings, different output caps, and possibly free on one and
|
|
@@ -644,7 +708,7 @@ an anthropic provider has no fixed model id to reshape on, so it needs an explic
|
|
|
644
708
|
**Prefer the pool form — do not pin one reshaper model:**
|
|
645
709
|
|
|
646
710
|
```jsonc
|
|
647
|
-
"reshaper": { "pool": "
|
|
711
|
+
"reshaper": { "pool": "medium" } // ranked candidates, tried in order
|
|
648
712
|
```
|
|
649
713
|
|
|
650
714
|
A pinned `{ "base": …, "model": … }` still works, but if the provider stops serving that exact id
|
|
@@ -684,9 +748,10 @@ llm-relay models --provider nim # one provider
|
|
|
684
748
|
llm-relay models --provider nim --refresh # force a re-fetch
|
|
685
749
|
```
|
|
686
750
|
|
|
687
|
-
On startup the proxy warms
|
|
688
|
-
|
|
689
|
-
|
|
751
|
+
On startup the proxy warms providers referenced by routing plus free/mixed providers that can
|
|
752
|
+
contribute to dynamic pools, then **warns about any routing target its provider doesn't serve**.
|
|
753
|
+
Unrelated subscription catalogs stay lazy until first use, while a stale/typo'd routed model is
|
|
754
|
+
still caught at boot rather than silently failing on its first request.
|
|
690
755
|
|
|
691
756
|
> Provider notes: **Groq** returns `403 "check your network settings"` from some
|
|
692
757
|
> IPs/regions (a network-side block, not a key issue) — it works once your network
|
|
@@ -720,7 +785,7 @@ no mode switch. So the tier map stays the default, and dispatcher-style usage is
|
|
|
720
785
|
"send namespaced ids + read `/registry`".
|
|
721
786
|
|
|
722
787
|
OpenAI-native clients can point their base URL at `http://127.0.0.1:8791/v1` and use a
|
|
723
|
-
namespaced model such as `anthropic/claude-sonnet-4-20250514` or `pool/
|
|
788
|
+
namespaced model such as `anthropic/claude-sonnet-4-20250514` or `pool/medium`. Codex uses
|
|
724
789
|
`/v1/responses`; other IDEs commonly use `/v1/chat/completions`. Configure the Anthropic
|
|
725
790
|
provider with `kind: "anthropic"` and `authEnv: "ANTHROPIC_API_KEY"` when the relay should
|
|
726
791
|
use its own key, or omit `authEnv` for an intentional caller-credential passthrough.
|
|
@@ -790,7 +855,7 @@ provider and point every tier at it:
|
|
|
790
855
|
"routing": {
|
|
791
856
|
"default": "anthropic",
|
|
792
857
|
"tiers": { "opus": "anthropic", "sonnet": "anthropic", "haiku": "anthropic", "fable": "anthropic" },
|
|
793
|
-
"pools": { "
|
|
858
|
+
"pools": { "medium": ["nim/z-ai/glm-5.2", "nim/deepseek-ai/deepseek-v4-pro"] }
|
|
794
859
|
}
|
|
795
860
|
```
|
|
796
861
|
|
package/config.example.json
CHANGED
|
@@ -38,55 +38,41 @@
|
|
|
38
38
|
}
|
|
39
39
|
},
|
|
40
40
|
"routing": {
|
|
41
|
-
"default":
|
|
42
|
-
"groq/llama-3.3-70b-versatile",
|
|
43
|
-
"openrouter/openrouter/free",
|
|
44
|
-
"nim/z-ai/glm-5.2",
|
|
45
|
-
"gemini/gemini-2.5-flash",
|
|
46
|
-
"ollama/qwen2.5-coder:32b"
|
|
47
|
-
],
|
|
41
|
+
"default": "pool/medium",
|
|
48
42
|
"tiers": {
|
|
49
|
-
"opus":
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
"sonnet": [
|
|
54
|
-
"groq/llama-3.3-70b-versatile",
|
|
55
|
-
"nim/z-ai/glm-5.2",
|
|
56
|
-
"gemini/gemini-2.5-flash",
|
|
57
|
-
"ollama/qwen2.5-coder:32b"
|
|
58
|
-
],
|
|
59
|
-
"haiku": [
|
|
60
|
-
"groq/llama-3.1-8b-instant",
|
|
61
|
-
"nim/meta/llama-3.1-8b-instruct",
|
|
62
|
-
"openrouter/openrouter/free",
|
|
63
|
-
"ollama/deepseek-r1:14b"
|
|
64
|
-
],
|
|
65
|
-
"fable": [
|
|
66
|
-
"groq/llama-3.1-8b-instant",
|
|
67
|
-
"ollama/deepseek-r1:14b"
|
|
68
|
-
]
|
|
43
|
+
"opus": "pool/xhigh",
|
|
44
|
+
"fable": "pool/xhigh",
|
|
45
|
+
"sonnet": "pool/high",
|
|
46
|
+
"haiku": "pool/medium"
|
|
69
47
|
},
|
|
70
48
|
"pools": {
|
|
71
|
-
"
|
|
72
|
-
"preferred": [
|
|
73
|
-
"include": "free"
|
|
49
|
+
"low": {
|
|
50
|
+
"preferred": [],
|
|
51
|
+
"include": "free",
|
|
52
|
+
"effort": "low"
|
|
74
53
|
},
|
|
75
|
-
"
|
|
76
|
-
"preferred": [
|
|
77
|
-
"include": "free"
|
|
54
|
+
"medium": {
|
|
55
|
+
"preferred": [],
|
|
56
|
+
"include": "free",
|
|
57
|
+
"effort": "medium"
|
|
78
58
|
},
|
|
79
|
-
"
|
|
80
|
-
"preferred": [
|
|
81
|
-
"include": "free"
|
|
59
|
+
"high": {
|
|
60
|
+
"preferred": [],
|
|
61
|
+
"include": "free",
|
|
62
|
+
"effort": "high"
|
|
63
|
+
},
|
|
64
|
+
"xhigh": {
|
|
65
|
+
"preferred": [],
|
|
66
|
+
"include": "free",
|
|
67
|
+
"effort": "xhigh"
|
|
82
68
|
}
|
|
83
69
|
},
|
|
84
70
|
"subagents": {
|
|
85
|
-
"opus": "pool/
|
|
86
|
-
"sonnet": "pool/
|
|
87
|
-
"haiku": "pool/
|
|
88
|
-
"fable": "pool/
|
|
89
|
-
"default": "pool/
|
|
71
|
+
"opus": "pool/xhigh",
|
|
72
|
+
"sonnet": "pool/high",
|
|
73
|
+
"haiku": "pool/medium",
|
|
74
|
+
"fable": "pool/xhigh",
|
|
75
|
+
"default": "pool/medium"
|
|
90
76
|
},
|
|
91
77
|
"offload": {
|
|
92
78
|
"claude": { "enabled": false, "scope": "subagents" },
|