modelrig 0.10.0 → 0.11.1
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 +96 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +1 -0
- package/dist/config.d.ts +18 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +23 -3
- package/dist/doctor-cli.d.ts.map +1 -1
- package/dist/doctor-cli.js +2 -1
- package/dist/doctor-preflight.d.ts +14 -2
- package/dist/doctor-preflight.d.ts.map +1 -1
- package/dist/doctor-preflight.js +65 -5
- package/dist/feedback/coverage.d.ts.map +1 -1
- package/dist/feedback/coverage.js +9 -2
- package/dist/index.d.ts +7 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +18 -8
- package/dist/init/init-cli.d.ts.map +1 -1
- package/dist/init/init-cli.js +21 -4
- package/dist/init/observe-cli.js +1 -1
- package/dist/init/observe.d.ts +7 -1
- package/dist/init/observe.d.ts.map +1 -1
- package/dist/init/observe.js +74 -22
- package/dist/init/starter.d.ts +36 -0
- package/dist/init/starter.d.ts.map +1 -0
- package/dist/init/starter.js +85 -0
- package/dist/run/raw-finalize.d.ts +49 -3
- package/dist/run/raw-finalize.d.ts.map +1 -1
- package/dist/run/raw-finalize.js +31 -14
- package/dist/run/raw-search-cost.d.ts +3 -2
- package/dist/run/raw-search-cost.d.ts.map +1 -1
- package/dist/run/raw-search-cost.js +1 -0
- package/dist/run/rawonly.d.ts +29 -5
- package/dist/run/rawonly.d.ts.map +1 -1
- package/dist/run/rawonly.js +54 -19
- package/dist/run/record-call.d.ts +104 -0
- package/dist/run/record-call.d.ts.map +1 -0
- package/dist/run/record-call.js +277 -0
- package/dist/telemetry/attempt-meta.d.ts +11 -0
- package/dist/telemetry/attempt-meta.d.ts.map +1 -1
- package/dist/telemetry/attempt-meta.js +3 -0
- package/dist/telemetry/lanes.d.ts +31 -0
- package/dist/telemetry/lanes.d.ts.map +1 -0
- package/dist/telemetry/lanes.js +41 -0
- package/dist/telemetry/org-identity.d.ts +5 -2
- package/dist/telemetry/org-identity.d.ts.map +1 -1
- package/dist/telemetry/org-identity.js +10 -8
- package/docs/agent-skill/SKILL.md +109 -10
- package/docs/how-it-fits.md +1 -1
- package/docs/instrumentation.md +152 -40
- package/docs/llms.txt +92 -9
- package/docs/quickstart.md +127 -15
- package/package.json +1 -1
package/docs/quickstart.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# ModelRig quickstart —
|
|
1
|
+
# ModelRig quickstart — See. Grade. Improve.
|
|
2
2
|
|
|
3
3
|
**The question this page answers: how do I get from nothing to my first run on `/runs`, with its steps and artifacts, in about ten minutes?**
|
|
4
4
|
|
|
@@ -11,11 +11,12 @@ and three things happen at once:
|
|
|
11
11
|
artifacts, lineaged and integrity-hashed; see the
|
|
12
12
|
[instrumentation guide](instrumentation.md)), with cost/tokens/tier and a
|
|
13
13
|
grade recorded against your tags.
|
|
14
|
-
- **
|
|
15
|
-
authorized, and you can replay that traffic against cheaper candidates in a
|
|
14
|
+
- **Grade** — you get schema-validated output, served only by a model you
|
|
15
|
+
authorized, you attach grades from signals you already have, and you can replay that traffic against cheaper candidates in a
|
|
16
16
|
bake-off before you change anything.
|
|
17
|
-
- **
|
|
18
|
-
|
|
17
|
+
- **Improve** — a winning candidate is a proposal you review, and nothing swaps
|
|
18
|
+
a model on its own. Never a silent swap: promote it in ModelRig (console or
|
|
19
|
+
MCP; versioned, reversible) or merge it as a PR if you keep routes in git.
|
|
19
20
|
|
|
20
21
|
The path is the same whoever does the work: **mint a key → install → confirm the
|
|
21
22
|
org → define a route → wrap one pipeline execution in a run → see it on `/runs`.**
|
|
@@ -102,9 +103,16 @@ import { createRig, loadConfigFromEnv } from "modelrig";
|
|
|
102
103
|
Environment (read once, in ModelRig's config layer only):
|
|
103
104
|
|
|
104
105
|
```bash
|
|
105
|
-
|
|
106
|
+
# Provider keys: only the providers you configure get adapters. Set the ones
|
|
107
|
+
# your routes (or runRaw calls) use; these are the names the SDK reads.
|
|
108
|
+
GEMINI_API_KEY=…
|
|
106
109
|
OPENAI_API_KEY=…
|
|
107
110
|
DEEPSEEK_API_KEY=…
|
|
111
|
+
ANTHROPIC_API_KEY=…
|
|
112
|
+
XAI_API_KEY=… # grok
|
|
113
|
+
ZAI_API_KEY=… # Z.ai / Zhipu
|
|
114
|
+
MODELRIG_DEEPINFRA_API_KEY=…
|
|
115
|
+
MODELRIG_FIREWORKS_API_KEY=…
|
|
108
116
|
|
|
109
117
|
# Where your telemetry goes — pick ONE lane:
|
|
110
118
|
MODELRIG_API_KEY=rig_sk_… # hosted: rows go to ModelRig,
|
|
@@ -119,14 +127,14 @@ MODELRIG_ENVELOPE_BUDGET_USD=25 # default envelope budget
|
|
|
119
127
|
### 2. Confirm the org
|
|
120
128
|
|
|
121
129
|
<!-- BLOCK:CONFIRM_ORG -->
|
|
122
|
-
**Confirm the org before you trust an empty console.** Run `modelrig doctor` from the same environment your app runs in. It asks GET /v1/whoami which organization the key belongs to
|
|
130
|
+
**Confirm the org before you trust an empty console.** Run `modelrig doctor` from the same environment your app runs in, once your app has started with the key. It asks GET /v1/whoami which organization the key belongs to and prints the org's name and, in parentheses, its slug; the name must match the org badge in the console header. If it names a different organization, your data is landing there: switch the console to that organization, or mint a key in the one you meant (https://modelrig.dev/orgs-and-keys.html).
|
|
123
131
|
|
|
124
132
|
```bash
|
|
125
133
|
npx modelrig doctor
|
|
126
|
-
# [modelrig] key rig_sk_…AbCd → org «Your Org» ✓ (ingest https://api.modelrig.ai, …)
|
|
134
|
+
# [modelrig] key rig_sk_…AbCd → org «Your Org» (your-org) ✓ (ingest https://api.modelrig.ai, …)
|
|
127
135
|
|
|
128
|
-
# No CLI in that environment? Ask the API directly:
|
|
129
|
-
curl -H "Authorization: Bearer $MODELRIG_API_KEY" https://api.modelrig.ai/v1/whoami
|
|
136
|
+
# No CLI in that environment? Ask the API directly (same host the SDK uses):
|
|
137
|
+
curl -H "Authorization: Bearer $MODELRIG_API_KEY" "${MODELRIG_INGEST_URL:-https://api.modelrig.ai}/v1/whoami"
|
|
130
138
|
```
|
|
131
139
|
<!-- /BLOCK:CONFIRM_ORG -->
|
|
132
140
|
|
|
@@ -176,6 +184,14 @@ CRITICAL: respond with a single JSON object, no markdown fences.
|
|
|
176
184
|
Variables must be declared in `prompt.variables`; referencing an undeclared
|
|
177
185
|
variable fails at load time, not at run time.
|
|
178
186
|
|
|
187
|
+
The route references two files next to it, and both must exist or the route
|
|
188
|
+
fails to load (`schema file not found` / `prompt.system file not found`).
|
|
189
|
+
`npx modelrig init` writes this starter route's two files,
|
|
190
|
+
`modelrig/routes/schemas/support_summarize.schema.json` and
|
|
191
|
+
`modelrig/routes/prompts/support_summarize.system.md`, without overwriting
|
|
192
|
+
files you already have. Then `npx modelrig validate` checks the bundle with no
|
|
193
|
+
provider key.
|
|
194
|
+
|
|
179
195
|
The `schema` file is a JSON Schema. ModelRig validates output under **JSON
|
|
180
196
|
Schema 2020-12** as the primary dialect and **also accepts draft-07** (a schema
|
|
181
197
|
declaring either `$schema` compiles). Validation runs non-strict with union
|
|
@@ -185,6 +201,23 @@ unless you have a draft-07 schema already; both work.
|
|
|
185
201
|
|
|
186
202
|
### 4. Call it
|
|
187
203
|
|
|
204
|
+
No route yet, because you are observing first or calling providers through a
|
|
205
|
+
`rig.runRaw` seam? You don't need one:
|
|
206
|
+
|
|
207
|
+
<!-- BLOCK:RAW_RIG -->
|
|
208
|
+
**No routes yet? Build the rig raw-only.** Observing a pipeline, or recording calls your code makes itself, needs no route files. Construct the rig with `routesDir: null`: `rig.runRaw`, `rig.runRawStream` and `rig.recordCall` work at once, and `rig.run` / `rig.runBundle` refuse with a teaching error until you add routes. Without it, createRig loads ./modelrig/routes: a directory you configured that is missing throws, and a missing default directory falls back to raw-only with a one-line warning.
|
|
209
|
+
|
|
210
|
+
```ts
|
|
211
|
+
import { createRig, loadConfigFromEnv } from "modelrig";
|
|
212
|
+
|
|
213
|
+
// Raw-only on purpose: no route files needed. rig.runRaw, rig.runRawStream and
|
|
214
|
+
// rig.recordCall work; rig.run needs routes (add them later, drop routesDir: null).
|
|
215
|
+
const rig = createRig({ ...loadConfigFromEnv(), routesDir: null });
|
|
216
|
+
```
|
|
217
|
+
<!-- /BLOCK:RAW_RIG -->
|
|
218
|
+
|
|
219
|
+
With the route above in place:
|
|
220
|
+
|
|
188
221
|
```ts
|
|
189
222
|
const rig = createRig(loadConfigFromEnv());
|
|
190
223
|
|
|
@@ -200,7 +233,7 @@ result.meta.costEstimateUsd; // priced from the pinned LiteLLM snapshot
|
|
|
200
233
|
result.meta.servedTier; // vs meta.requestedTier — downgrades visible
|
|
201
234
|
result.meta.attemptsByClass; // retries consumed, per failure class
|
|
202
235
|
|
|
203
|
-
rig.close();
|
|
236
|
+
await rig.close(); // flushes telemetry; await it once at shutdown (R6)
|
|
204
237
|
```
|
|
205
238
|
|
|
206
239
|
### 5. Wrap it in a run — the standard path
|
|
@@ -240,6 +273,37 @@ await rig.close(); // flushes the exporter — call once at shutdown
|
|
|
240
273
|
|
|
241
274
|
- **Raw lane:** a `rig.runRaw` call inside a run context records a ground-truth
|
|
242
275
|
step too (since 0.5.0), so grounded/cached pipelines fill the same graph.
|
|
276
|
+
- **A call your code makes itself** (a direct provider client, a stream, an
|
|
277
|
+
embedding) is invisible until you record it:
|
|
278
|
+
|
|
279
|
+
<!-- BLOCK:RECORD_CALL -->
|
|
280
|
+
**Record a call your code makes itself.** A provider call your code makes directly (a client ModelRig has no lane for, a stream you read yourself, an embedding, a fallback) is invisible until you report it. Call `rig.recordCall` right after it returns, inside the run, with the tokens the provider reported: the call shows on /inferences as "recorded · unverified", joins its run and step, and counts toward cost. It is synchronous and never throws; invalid input warns once and records nothing (`inferenceId: null`). Spread optional fields only when the provider reported them, so the call type-checks under `exactOptionalPropertyTypes`.
|
|
281
|
+
|
|
282
|
+
```ts
|
|
283
|
+
const started = Date.now();
|
|
284
|
+
const resp = await genai.models.generateContent({ model: "gemini-3.1-pro-preview", contents });
|
|
285
|
+
const u = resp.usageMetadata;
|
|
286
|
+
const finishReason = resp.candidates?.[0]?.finishReason;
|
|
287
|
+
|
|
288
|
+
const rec = rig.recordCall({
|
|
289
|
+
provider: "gemini",
|
|
290
|
+
model: "gemini-3.1-pro-preview",
|
|
291
|
+
usage: {
|
|
292
|
+
tokensIn: u?.promptTokenCount ?? 0, // total input, cached included
|
|
293
|
+
tokensOut: (u?.candidatesTokenCount ?? 0) + (u?.thoughtsTokenCount ?? 0),
|
|
294
|
+
tokensCached: u?.cachedContentTokenCount ?? 0,
|
|
295
|
+
// optional fields: spread only when the provider reported them
|
|
296
|
+
...(u?.thoughtsTokenCount !== undefined ? { reasoningTokens: u.thoughtsTokenCount } : {}),
|
|
297
|
+
},
|
|
298
|
+
servedTier: "standard", // the tier that SERVED the call: pass what the provider reports (flex, priority); it is priced at that tier
|
|
299
|
+
latencyMs: Date.now() - started,
|
|
300
|
+
stepKey: "summarize", // the step on /runs (inside run.scope / run.start)
|
|
301
|
+
...(finishReason !== undefined ? { finishReason } : {}),
|
|
302
|
+
});
|
|
303
|
+
if (rec.inferenceId !== null) rig.grade({ kind: "inference", id: rec.inferenceId }, { score: 1, kind: "deterministic" });
|
|
304
|
+
```
|
|
305
|
+
<!-- /BLOCK:RECORD_CALL -->
|
|
306
|
+
|
|
243
307
|
- **Reserved tags:** `run_id` and `step` are stamped by the SDK; the full list of
|
|
244
308
|
reserved keys is in the
|
|
245
309
|
[instrumentation guide](instrumentation.md#reserved-tag-keys-the-one-list).
|
|
@@ -260,7 +324,9 @@ warns if a rig records calls but never starts a run. If the page is empty:
|
|
|
260
324
|
```text
|
|
261
325
|
1. npx modelrig doctor, from the environment your app runs in.
|
|
262
326
|
no export sink configured → this process does not see MODELRIG_API_KEY: set it, redeploy, open a new shell.
|
|
327
|
+
malformed key → the value is truncated, quoted or padded: paste the whole rig_sk_… secret again.
|
|
263
328
|
AUTH FAILED (401 / 403) → the key is mistyped, revoked or not accepted: mint a key, set it, redeploy, open a new shell.
|
|
329
|
+
whoami unreachable → no network path to the ingest host (firewall, proxy, wrong MODELRIG_INGEST_URL): fix the route out, retry.
|
|
264
330
|
2. The org doctor prints is not the org in the badge → wrong org: switch the console, or mint a key in the org you meant.
|
|
265
331
|
3. /inferences has rows but /runs is empty → the calls are not inside run.scope / run.start.
|
|
266
332
|
4. A run is missing a step or a cost → that call is not on a ModelRig lane (see not-captured).
|
|
@@ -268,6 +334,48 @@ warns if a rig records calls but never starts a run. If the page is empty:
|
|
|
268
334
|
```
|
|
269
335
|
<!-- /BLOCK:EMPTY_CONSOLE -->
|
|
270
336
|
|
|
337
|
+
### 7. Grade it, then prove a cheaper model
|
|
338
|
+
|
|
339
|
+
<!-- BLOCK:GRADE_RUN -->
|
|
340
|
+
**Attach a grade to a call or a whole run.** A grade is your judgment: a 0–1 score with an optional comment, from a validator, a downstream accept/reject, a human or a model. Grade one call by its inference id (`result.meta.inferenceId`, or the `inferenceId` rig.recordCall returns). Grade a whole run by its run id, `run.runId` on the handle `run.start` returns: that grade shows on the run page and on /projects. A run grade whose id is the run's `episodeKey` also shows, on the run page only. `run_id` is a reserved tag the SDK stamps, not a variable your code has. A grade whose id matches no call or run attaches to nothing. The write is synchronous and local and never throws into your pipeline.
|
|
341
|
+
|
|
342
|
+
```ts
|
|
343
|
+
const run = rig.artifacts.run.start({ pipeline: "support", episodeKey: ticketId, environment: "production" });
|
|
344
|
+
const result = await rig.run("example.support_summarize", {
|
|
345
|
+
input: { ticket, product, priorContext },
|
|
346
|
+
tags: { subject: customer },
|
|
347
|
+
stepKey: "summarize",
|
|
348
|
+
});
|
|
349
|
+
run.end("succeeded");
|
|
350
|
+
|
|
351
|
+
// one call: the inference id rides on every result
|
|
352
|
+
rig.grade({ kind: "inference", id: result.meta.inferenceId }, { score: 1, kind: "deterministic", grader: "schema-check" });
|
|
353
|
+
// the whole run: its id is run.runId from run.start (shows on the run page and on /projects)
|
|
354
|
+
rig.grade({ kind: "run", id: run.runId }, { score: 0, kind: "human", comment: "missed the refund request" });
|
|
355
|
+
```
|
|
356
|
+
<!-- /BLOCK:GRADE_RUN -->
|
|
357
|
+
|
|
358
|
+
<!-- BLOCK:BAKEOFF -->
|
|
359
|
+
**Prove a cheaper model on your own traffic.** A bake-off replays your own inputs through the variants a route declares and reports the effective cost of conformance for each. `--variants` names entries in the route's `variants:` block (`default` is the route as declared), never model ids, and a variant can only narrow the route's candidates. Set `capture: true` first: replay works on traffic captured after that, not before. With no captured traffic (a run-based pipeline), promote a real /runs output to a golden (the console's make golden, or `promoteToEval`) and use `--from-eval-cases <task>`; its variants come from the route bundle named after the task. A bake-off measures and never switches anything.
|
|
360
|
+
|
|
361
|
+
```bash
|
|
362
|
+
# 1. in modelrig/routes/example.support_summarize.yaml: capture traffic, and declare
|
|
363
|
+
# a cheaper variant (narrowing only: its candidates must be in the route's list)
|
|
364
|
+
capture: true
|
|
365
|
+
variants:
|
|
366
|
+
- name: cheap
|
|
367
|
+
candidates:
|
|
368
|
+
- provider: gemini
|
|
369
|
+
model: gemini-3.1-flash-lite
|
|
370
|
+
|
|
371
|
+
# 2. after traffic accumulates, compare the route as declared (default) with cheap
|
|
372
|
+
npx modelrig bakeoff --route example.support_summarize --variants default,cheap --replay-last 50
|
|
373
|
+
|
|
374
|
+
# no captured route traffic? promote a real /runs output to a golden, then
|
|
375
|
+
npx modelrig bakeoff --from-eval-cases example.support_summarize --variants default,cheap
|
|
376
|
+
```
|
|
377
|
+
<!-- /BLOCK:BAKEOFF -->
|
|
378
|
+
|
|
271
379
|
## Change your route's model live (no redeploy)
|
|
272
380
|
|
|
273
381
|
A route's **call-config** — which model/provider serves it, and its sampling — can be
|
|
@@ -375,7 +483,9 @@ const { bundle: runnable, arm } = resolveCallConfigForRun(bundle, resolved, {
|
|
|
375
483
|
input, // stickyKey (if set) reads its unit from here…
|
|
376
484
|
// unitKey: userId, // …or pass the unit key explicitly (sticky assignment)
|
|
377
485
|
});
|
|
378
|
-
|
|
486
|
+
// arm is string | null and RunOptions.arm is optional: pass it only when one was
|
|
487
|
+
// drawn (a bare `arm` fails to compile under exactOptionalPropertyTypes).
|
|
488
|
+
const result = await rig.runBundle(runnable, { input, tags, ...(arm !== null ? { arm } : {}) });
|
|
379
489
|
// arm is null for a pin / no-override / non-experiment call ⇒ no meta.arm (byte-identical).
|
|
380
490
|
```
|
|
381
491
|
|
|
@@ -419,7 +529,7 @@ key never burns backoff time. Failures that still billed tokens (refusals,
|
|
|
419
529
|
truncations) carry their token usage, so envelopes and telemetry account the
|
|
420
530
|
real spend.
|
|
421
531
|
|
|
422
|
-
Each class — what it means, whether it retries, and how it backs off — is on [Routing & reliability](routing-reliability.md#5-failure-classes); how the whole candidate ladder runs
|
|
532
|
+
Each class — what it means, whether it retries, and how it backs off — is on [Routing & reliability](routing-reliability.md#5-failure-classes); that page also covers how the whole candidate ladder runs: the ordering, the validate → repair → fall-through sequence, and the parts that are not built yet.
|
|
423
533
|
|
|
424
534
|
## Where the telemetry goes
|
|
425
535
|
|
|
@@ -427,7 +537,9 @@ Every attempt (not just every run) writes one row to package-owned SQLite,
|
|
|
427
537
|
tagged with your `tags` — always, with no key. Setting `MODELRIG_API_KEY` turns
|
|
428
538
|
on **api export mode**: a background exporter ships batches to the ingest API
|
|
429
539
|
(`api.modelrig.ai`, override with `MODELRIG_INGEST_URL`), which the console
|
|
430
|
-
reads — fire-and-forget
|
|
540
|
+
reads — fire-and-forget: a transient failure (network, 5xx) is retried, a
|
|
541
|
+
rejected key (401/403) drops the rows and logs `AUTH FAILED`; the inference path
|
|
542
|
+
never blocks on it.
|
|
431
543
|
Inspect live rows in your local SQLite file, or in the console once the key is
|
|
432
544
|
set — never a database URL of your own.
|
|
433
545
|
|
|
@@ -438,7 +550,7 @@ only the web tier silently loses every row a worker or cron produced.
|
|
|
438
550
|
|
|
439
551
|
Turning **Optimization on** for a route (`capture: true`) is the **recommended
|
|
440
552
|
default** — your traffic is what makes the routing smarter and the bill smaller,
|
|
441
|
-
and everything it keeps you can see
|
|
553
|
+
and everything it keeps you can see and export in the console. Keeping the
|
|
442
554
|
prompts, outputs and evidence too is [content custody](content-custody.md), which
|
|
443
555
|
is **on by default** — every new org is created managed, having accepted the
|
|
444
556
|
published terms at signup — and captures into your managed store, scrubbed for the
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "modelrig",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "ModelRig — capability-aware AI gateway: routes-as-code, provider adapters, typed failures, budget envelopes, and local-first telemetry.",
|
|
6
6
|
"license": "Apache-2.0",
|