modelrig 0.5.0 → 0.7.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 +125 -0
- package/README.md +54 -4
- package/dist/adapters/anthropic.d.ts.map +1 -1
- package/dist/adapters/anthropic.js +36 -7
- package/dist/adapters/attempt-meta-map.d.ts +63 -0
- package/dist/adapters/attempt-meta-map.d.ts.map +1 -0
- package/dist/adapters/attempt-meta-map.js +154 -0
- package/dist/adapters/deepseek.d.ts.map +1 -1
- package/dist/adapters/deepseek.js +25 -2
- package/dist/adapters/gemini.d.ts.map +1 -1
- package/dist/adapters/gemini.js +29 -2
- package/dist/adapters/grok.d.ts.map +1 -1
- package/dist/adapters/grok.js +25 -2
- package/dist/adapters/openai-compat.d.ts.map +1 -1
- package/dist/adapters/openai-compat.js +25 -2
- package/dist/adapters/openai-stream.d.ts +2 -0
- package/dist/adapters/openai-stream.d.ts.map +1 -1
- package/dist/adapters/openai-stream.js +23 -3
- package/dist/adapters/openai.d.ts.map +1 -1
- package/dist/adapters/openai.js +36 -6
- package/dist/artifacts/artifact.d.ts.map +1 -1
- package/dist/artifacts/artifact.js +24 -5
- package/dist/artifacts/classification.d.ts +27 -0
- package/dist/artifacts/classification.d.ts.map +1 -0
- package/dist/artifacts/classification.js +38 -0
- package/dist/artifacts/index.d.ts +3 -1
- package/dist/artifacts/index.d.ts.map +1 -1
- package/dist/artifacts/index.js +10 -1
- package/dist/artifacts/run-context.d.ts +25 -3
- package/dist/artifacts/run-context.d.ts.map +1 -1
- package/dist/artifacts/run-context.js +61 -0
- package/dist/artifacts/scrub.d.ts +26 -0
- package/dist/artifacts/scrub.d.ts.map +1 -1
- package/dist/artifacts/scrub.js +53 -0
- package/dist/artifacts/step.d.ts +9 -0
- package/dist/artifacts/step.d.ts.map +1 -1
- package/dist/artifacts/step.js +9 -0
- package/dist/bakeoff/engine.d.ts +46 -4
- package/dist/bakeoff/engine.d.ts.map +1 -1
- package/dist/bakeoff/engine.js +269 -19
- package/dist/bakeoff/eval-cases.d.ts +5 -0
- package/dist/bakeoff/eval-cases.d.ts.map +1 -1
- package/dist/bakeoff/eval-cases.js +3 -0
- package/dist/bakeoff/judge.d.ts +39 -0
- package/dist/bakeoff/judge.d.ts.map +1 -0
- package/dist/bakeoff/judge.js +102 -0
- package/dist/bakeoff/quality.d.ts +44 -0
- package/dist/bakeoff/quality.d.ts.map +1 -0
- package/dist/bakeoff/quality.js +126 -0
- package/dist/bakeoff/reference.d.ts +34 -0
- package/dist/bakeoff/reference.d.ts.map +1 -0
- package/dist/bakeoff/reference.js +68 -0
- package/dist/bakeoff/replay.d.ts +25 -0
- package/dist/bakeoff/replay.d.ts.map +1 -1
- package/dist/bakeoff/replay.js +48 -1
- package/dist/bakeoff/report.d.ts +8 -1
- package/dist/bakeoff/report.d.ts.map +1 -1
- package/dist/bakeoff/report.js +83 -12
- package/dist/bakeoff/samples.d.ts +35 -2
- package/dist/bakeoff/samples.d.ts.map +1 -1
- package/dist/bakeoff/samples.js +67 -6
- package/dist/bakeoff/stats.d.ts +17 -1
- package/dist/bakeoff/stats.d.ts.map +1 -1
- package/dist/bakeoff/stats.js +67 -0
- package/dist/bakeoff/types.d.ts +76 -5
- package/dist/bakeoff/types.d.ts.map +1 -1
- package/dist/bakeoff/types.js +4 -3
- package/dist/cli-query.d.ts +14 -0
- package/dist/cli-query.d.ts.map +1 -0
- package/dist/cli-query.js +59 -0
- package/dist/cli.d.ts +2 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +26 -23
- package/dist/config.d.ts +6 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +14 -0
- package/dist/detect/detector.d.ts +82 -0
- package/dist/detect/detector.d.ts.map +1 -0
- package/dist/detect/detector.js +23 -0
- package/dist/index.d.ts +19 -14
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +38 -11
- package/dist/query/analytics.d.ts +84 -0
- package/dist/query/analytics.d.ts.map +1 -0
- package/dist/query/analytics.js +280 -0
- package/dist/query/columns.d.ts +59 -0
- package/dist/query/columns.d.ts.map +1 -0
- package/dist/query/columns.js +89 -0
- package/dist/query/grammar.d.ts +74 -7
- package/dist/query/grammar.d.ts.map +1 -1
- package/dist/query/grammar.js +179 -71
- package/dist/query/saved.d.ts +26 -0
- package/dist/query/saved.d.ts.map +1 -0
- package/dist/query/saved.js +51 -0
- package/dist/routes/loader.js +15 -10
- package/dist/routes/objective-loader.d.ts +35 -0
- package/dist/routes/objective-loader.d.ts.map +1 -0
- package/dist/routes/objective-loader.js +286 -0
- package/dist/run/accept.d.ts +43 -0
- package/dist/run/accept.d.ts.map +1 -0
- package/dist/run/accept.js +65 -0
- package/dist/run/cost.d.ts +13 -0
- package/dist/run/cost.d.ts.map +1 -1
- package/dist/run/cost.js +27 -0
- package/dist/run/dispatch.d.ts +24 -1
- package/dist/run/dispatch.d.ts.map +1 -1
- package/dist/run/dispatch.js +19 -0
- package/dist/run/ladder-key.d.ts +4 -0
- package/dist/run/ladder-key.d.ts.map +1 -1
- package/dist/run/ladder-key.js +3 -0
- package/dist/run/quality-gate.d.ts +39 -0
- package/dist/run/quality-gate.d.ts.map +1 -0
- package/dist/run/quality-gate.js +70 -0
- package/dist/run/raw.d.ts.map +1 -1
- package/dist/run/raw.js +22 -3
- package/dist/run/record-attempt.d.ts.map +1 -1
- package/dist/run/record-attempt.js +22 -1
- package/dist/run/routing-docs.d.ts.map +1 -1
- package/dist/run/routing-docs.js +11 -4
- package/dist/run/run.d.ts.map +1 -1
- package/dist/run/run.js +46 -39
- package/dist/settings/index.d.ts +1 -1
- package/dist/settings/index.d.ts.map +1 -1
- package/dist/settings/index.js +2 -1
- package/dist/settings/task-settings.d.ts +10 -0
- package/dist/settings/task-settings.d.ts.map +1 -1
- package/dist/settings/task-settings.js +17 -0
- package/dist/telemetry/attempt-meta.d.ts +178 -0
- package/dist/telemetry/attempt-meta.d.ts.map +1 -0
- package/dist/telemetry/attempt-meta.js +319 -0
- package/dist/telemetry/exporter.d.ts.map +1 -1
- package/dist/telemetry/exporter.js +6 -0
- package/dist/telemetry/ingest-protocol.d.ts +11 -0
- package/dist/telemetry/ingest-protocol.d.ts.map +1 -1
- package/dist/telemetry/ingest-protocol.js +3 -0
- package/dist/telemetry/otel-map.d.ts +10 -0
- package/dist/telemetry/otel-map.d.ts.map +1 -1
- package/dist/telemetry/otel-map.js +64 -0
- package/dist/telemetry/store-migrations.d.ts.map +1 -1
- package/dist/telemetry/store-migrations.js +15 -0
- package/dist/telemetry/store.d.ts.map +1 -1
- package/dist/telemetry/store.js +25 -7
- package/dist/types/failures-teaching.d.ts.map +1 -1
- package/dist/types/failures-teaching.js +20 -0
- package/dist/types/failures.d.ts +1 -1
- package/dist/types/failures.d.ts.map +1 -1
- package/dist/types/failures.js +1 -0
- package/dist/types/provider.d.ts +9 -0
- package/dist/types/provider.d.ts.map +1 -1
- package/dist/types/quality.d.ts +41 -0
- package/dist/types/quality.d.ts.map +1 -0
- package/dist/types/quality.js +12 -0
- package/dist/types/route.d.ts +89 -0
- package/dist/types/route.d.ts.map +1 -1
- package/dist/types/telemetry.d.ts +13 -0
- package/dist/types/telemetry.d.ts.map +1 -1
- package/dist/validate-cli.d.ts.map +1 -1
- package/dist/validate-cli.js +10 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -10,6 +10,131 @@ published package diverge.
|
|
|
10
10
|
This project adheres to [Semantic Versioning](https://semver.org/). Dates are
|
|
11
11
|
UTC.
|
|
12
12
|
|
|
13
|
+
## [0.7.0] — 2026-08-23
|
|
14
|
+
|
|
15
|
+
Released with the docs/registry mirror in one R-A ceremony. Headline: wrap a
|
|
16
|
+
pipeline in `rig.artifacts.run.start({ pipeline, episodeKey })` and that is the
|
|
17
|
+
whole instrumentation story — the SDK stamps `tags.run_id` on every call of both
|
|
18
|
+
lanes, so `/runs` AND `/projects` fill with no per-call tag plumbing.
|
|
19
|
+
|
|
20
|
+
Run-context tags (RC-1) — inside `rig.artifacts.run.start` / `run.scope`, the SDK
|
|
21
|
+
stamps `tags.run_id` (the run's `episodeKey`, else its id) on every attempt row of
|
|
22
|
+
both lanes when the caller passed none; a caller-supplied `run_id` always wins,
|
|
23
|
+
and outside a run context rows are byte-identical. The console `/projects` tree
|
|
24
|
+
joins a uuid-valued `run_id` by `runs.id` as well as by `episode_key`, so a
|
|
25
|
+
pipeline that calls `run.start({ pipeline })` alone now fills `/runs` AND
|
|
26
|
+
`/projects`. `ActiveRunContext.episodeKey` is exposed; `withRunIdTag` is the one
|
|
27
|
+
rule. ZERO DDL.
|
|
28
|
+
|
|
29
|
+
## [0.6.0] — 2026-08-23
|
|
30
|
+
|
|
31
|
+
Released with the docs/registry mirror in one R-A ceremony. Headline for router
|
|
32
|
+
codebases: the candidate ladder can now fall through on **quality** (`qualityGate`
|
|
33
|
+
→ `quality_rejected`), bake-offs measure **regret against a reference arm** under a
|
|
34
|
+
declared objective (legacy routes stay cost-first, byte-identical), the console
|
|
35
|
+
gains the **project tree** (`/projects`) and the **analytics API**
|
|
36
|
+
(`/v1/analytics/meta` + `/query`, metadata only — never content), and the
|
|
37
|
+
OpenAI-compatible proxy accepts the `developer` role. ZERO DDL across the line.
|
|
38
|
+
|
|
39
|
+
Regret-bounded bake-offs (RB-1, near-optimal) — the bake-off objective flips from
|
|
40
|
+
"cheapest that passes" to **near-optimal at a reasonable price**. New additive
|
|
41
|
+
route YAML: `objective` (`mode: quality-first | balanced | cost-first`,
|
|
42
|
+
`max_regret`, `price_ceiling`, `lambda`), `bakeoff` (`reference: auto | provider/model`,
|
|
43
|
+
`reference_envelope_usd`, `judge: { model, max_samples }`), and `value_checks`
|
|
44
|
+
(JSON-pointer assertions). Every bake-off can run a **reference (ceiling) arm**
|
|
45
|
+
and report each arm's **regret vs the reference with a seeded-bootstrap CI and n**.
|
|
46
|
+
Quality per sample resolves eval-case golden → `value_checks` → an opt-in capped,
|
|
47
|
+
metered judge (judge model ∈ declared candidates) → 1/0 conformance; it composes
|
|
48
|
+
with Q1's `qualityGate` (deterministic checks first, then the gate/judge). The
|
|
49
|
+
proposal gate is objective-aware: `cost-first` keeps Q1's gate byte-identical
|
|
50
|
+
(golden); a declared `quality-first`/`balanced` objective with no bound reports
|
|
51
|
+
regret but proposes nothing (no silent actuation). `modelrig bakeoff --objective
|
|
52
|
+
<mode>` overrides per run. The report/PR body state regret, cost and the ceiling
|
|
53
|
+
on one line. ZERO DDL. See `docs/bakeoffs.md` §5.
|
|
54
|
+
|
|
55
|
+
Analytics surface (WI-1b) — the read side of the attempt envelope. `GET
|
|
56
|
+
/v1/analytics/meta` describes every dimension and metric with its classification
|
|
57
|
+
and `since` version; `POST /v1/analytics/query` runs the SELECT-only grammar over
|
|
58
|
+
an org's inferences (metadata only — enums, numbers, hashes, identifiers; never
|
|
59
|
+
content; bake-off replay rows excluded). `modelrig query "<grammar>"` /
|
|
60
|
+
`--saved <id>` run the same grammar locally. Console: saved questions on
|
|
61
|
+
`/costs`, `/coverage`, `/routes/[route]`. OTLP-out gains `modelrig.meta.*` span
|
|
62
|
+
attributes for the metadata-safe envelope fields. Behaviour change: a free-text
|
|
63
|
+
`actor` is dropped with one warning (metadata-safe rule). New root exports:
|
|
64
|
+
`parseQuery`, `compileQuery`, `evaluateAnalyticsQuery`, `describeAnalyticsSchema`,
|
|
65
|
+
`planAnalyticsRead`, `runTelemetryQuery`, `SAVED_QUERIES`, `savedQueryById`, the
|
|
66
|
+
column registries and their types.
|
|
67
|
+
ZERO DDL.
|
|
68
|
+
|
|
69
|
+
Quality gate (#223, Q1) — the candidate ladder can now fall through on *quality*,
|
|
70
|
+
not just shape. Pass `rig.run(route, { qualityGate })` and a schema-valid output
|
|
71
|
+
your predicate rejects is recorded as a new `quality_rejected` failure (its own
|
|
72
|
+
retry budget, never the repair rung) and the ladder advances. The same predicate
|
|
73
|
+
scores bake-offs (`rig.bakeoff({ qualityGate })`) — a quality pass rate and an
|
|
74
|
+
`effectiveUsdPer1kPassing` metric beside the conformant one. SDK lane only: a
|
|
75
|
+
function cannot cross the hosted boundary; the route-declared hosted judge is Q2.
|
|
76
|
+
|
|
77
|
+
### Added — quality gate (#223, Q1)
|
|
78
|
+
|
|
79
|
+
- **`RunOptions.qualityGate?`** — a builder predicate
|
|
80
|
+
`(output, meta) => { ok, score?, reason? }` (sync or async) evaluated on a
|
|
81
|
+
schema-VALID output before it is accepted. `ok:false` records a
|
|
82
|
+
`quality_rejected` attempt (message carries the reason + score), captures the
|
|
83
|
+
rejected text for replay, and falls the ladder through on the class's OWN retry
|
|
84
|
+
budget (`policy.retries.quality_rejected`, default 0) — the repair rung is NEVER
|
|
85
|
+
entered from this path. A throwing predicate rejects fail-closed (`reason:
|
|
86
|
+
"qualityGate threw: …"`, one warning per process). Tool-call turns and
|
|
87
|
+
`schema: null` routes are handled per spec §4.1. `runStream` inherits it.
|
|
88
|
+
- **`FailureClass "quality_rejected"`** — retryable (own non-fungible budget),
|
|
89
|
+
never terminal, with an errors-that-teach entry (`docsUrl`
|
|
90
|
+
→ routing-reliability.html#quality_rejected) and a suggested `policy.retries`
|
|
91
|
+
edit. `policy.retries.quality_rejected` is accepted by the loader.
|
|
92
|
+
- **`RunMeta.quality?`** `{ score, verdict, judgedBy }` — present when a gate ran
|
|
93
|
+
(`verdict: "pass"` on a returned run; `judgedBy: null` for an SDK predicate).
|
|
94
|
+
SDK-owned `tags.quality` / `tags.quality_score` are stamped on the attempt row.
|
|
95
|
+
- **`rig.bakeoff` / `rig.bakeoffFromEvalCases` gain `qualityGate?`** — scored on
|
|
96
|
+
each replayed sample into `ReplaySampleResult.valueAccuracy` + `qualityPassed`;
|
|
97
|
+
`VariantMetrics` gains `qualityPassCi95` and `effectiveUsdPer1kPassing` (beside,
|
|
98
|
+
never redefining, `effectiveUsdPer1kConformant`), and `computeProposal` gains a
|
|
99
|
+
third clause (challenger quality-pass CI-lower ≥ incumbent's). The `modelrig
|
|
100
|
+
bakeoff` CLI cannot carry a function — use programmatic `rig.bakeoff` until Q2.
|
|
101
|
+
- **Exports:** `QualityGate`, `QualityVerdict`, `QualityGateMeta` from the root.
|
|
102
|
+
- **Boundary B1 rewritten** to "Route-declared judge (hosted lane)" (`not_built`,
|
|
103
|
+
tripwire `/\bjudge\??\s*:/`) now the SDK predicate ships; the routing-reliability
|
|
104
|
+
page, llms.txt (C11), README, `/definitions`, and the v4 setup prompt are swept
|
|
105
|
+
to match.
|
|
106
|
+
|
|
107
|
+
Project tree (G2b) — the console gains `/projects` (picker) and `/projects/[value]`
|
|
108
|
+
(project → run → step → call → attempts, cost + quality + lane on every node) over
|
|
109
|
+
one new read, `GET /v1/console/tree`. Server + console only; the SDK change below
|
|
110
|
+
is documentation. ZERO DDL.
|
|
111
|
+
|
|
112
|
+
- **`@step` / `step()` sets the ambient step key.** Documented in `step.ts` that
|
|
113
|
+
entering a step scope mints `label#n` and makes it the run context's ambient
|
|
114
|
+
step: an `artifact.save` with no explicit `stepKey` attaches to it — for saves
|
|
115
|
+
inside the scope AND for saves after it, until the next step mints a new key
|
|
116
|
+
(the ambient key is a running "most recent step", not a lexical bracket).
|
|
117
|
+
`run-context.ts` clarifies that within one run's parallel step waves only an
|
|
118
|
+
explicit `stepKey` prevents cross-attribution (`run.scope()` separates distinct
|
|
119
|
+
runs, not overlapping steps in one run).
|
|
120
|
+
|
|
121
|
+
Screening (B0 + B1) — two bug-class fixes and a shared detector contract.
|
|
122
|
+
- **Bake-off review samples are scrubbed before they persist.** A `--keep-outputs`
|
|
123
|
+
bake-off now runs each sample's input and output through the rig's content-custody
|
|
124
|
+
scrub mode (`detect` counts onto `arm_meta.scrub`, `redact` masks, `block` keeps a
|
|
125
|
+
metadata-only sample). Default `off` ⇒ rows byte-identical to before.
|
|
126
|
+
- **Proxy role enum.** `messages[].role` must be `system|developer|user|assistant|tool`
|
|
127
|
+
(`developer` normalises to the system slot); anything else is rejected with a
|
|
128
|
+
teaching message before dispatch (HTTP 422). Previously an arbitrary role was cast
|
|
129
|
+
through.
|
|
130
|
+
- **Raw-lane multi-turn encoding (behaviour change).** A genuine multi-turn transcript
|
|
131
|
+
is now flattened into the adapter's user prompt as one JSON object per line instead
|
|
132
|
+
of `role: content` text, so a crafted turn can never read as a synthetic system
|
|
133
|
+
line. Single-user-turn (and system + single user) requests are byte-identical.
|
|
134
|
+
- **Shared `Detector` interface.** `Detector`, `DetectionSpan`, `detectSpans`, and
|
|
135
|
+
`scrubDetectors` are exported from the package root; `scrubContent` is unchanged.
|
|
136
|
+
`buildBakeoffSamples` gains a scrub-resolver argument.
|
|
137
|
+
|
|
13
138
|
## [0.5.0] — 2026-08-22
|
|
14
139
|
|
|
15
140
|
Released with the docs/registry mirror in one R-A ceremony. Headline for router
|
package/README.md
CHANGED
|
@@ -145,7 +145,8 @@ provider's list price (zero ModelRig margin) and throws a typed `RigFailureError
|
|
|
145
145
|
on failure, exactly like `rig.run`.
|
|
146
146
|
|
|
147
147
|
Called inside a `rig.artifacts.run.start` context, `runRaw`/`runRawStream` also
|
|
148
|
-
records a ground-truth **step** in the run's graph (`/runs/[id]`)
|
|
148
|
+
records a ground-truth **step** in the run's graph (`/runs/[id]`) and carries the
|
|
149
|
+
run's `run_id` tag — since
|
|
149
150
|
modelrig 0.5.0, on success and on failure. Name it with `stepKey` (the same field
|
|
150
151
|
`RunOptions` exposes on the routed lane; precedence `stepKey` > `tags.step` > an
|
|
151
152
|
auto `provider/model#n` key), so a raw call and the artifacts you `save` around it
|
|
@@ -179,9 +180,55 @@ Every attempt is recorded in a local SQLite file (`.modelrig/telemetry.db`)
|
|
|
179
180
|
tagged with your `tags`. Failures are typed —
|
|
180
181
|
`RigFailureError.failure.class` is one of `content_invalid`, `capacity_shed`,
|
|
181
182
|
`network`, `refusal`, `cache_invalid`, `timeout`, `config_auth`, `budget_exhausted`,
|
|
182
|
-
`invariant_violation` — with a separate retry budget per class, so a flaky
|
|
183
|
+
`invariant_violation`, `quality_rejected` — with a separate retry budget per class, so a flaky
|
|
183
184
|
network never spends your content-validation retries. What each class means, whether it retries, and how it backs off is on [Routing & reliability](https://modelrig.dev/routing-reliability.html#5-failure-classes).
|
|
184
185
|
|
|
186
|
+
## Metadata capture ("capture now, analyze later")
|
|
187
|
+
|
|
188
|
+
Every inference records a versioned, **metadata-safe** attempt envelope
|
|
189
|
+
(`AttemptMeta@v1`) on `inferences.meta` — in both the routed and the raw lane,
|
|
190
|
+
from the same producer. It is the metadata the adapters already see and used to
|
|
191
|
+
discard: finish reason, refusal, reasoning tokens, the provider-reported model
|
|
192
|
+
snapshot and request id, coarse HTTP status / error code, sampling echo, tool
|
|
193
|
+
names (declared kept, undeclared hashed) with counts, per-component cost, request
|
|
194
|
+
and output **hashes** (never the content), request/response byte sizes, response
|
|
195
|
+
char count, and a coarse context bucket.
|
|
196
|
+
|
|
197
|
+
**It never contains free text.** Every field is an enum, a number, a boolean, a
|
|
198
|
+
hash, or an identifier. The producer drops any string that is over 128 chars or
|
|
199
|
+
contains whitespace, and the hosted ingest endpoint re-applies the same rule as a
|
|
200
|
+
backstop — so analysis later is possible without prompts or outputs ever being
|
|
201
|
+
stored here. Building the envelope never blocks or changes a run: on any failure
|
|
202
|
+
it is simply absent (`meta` is null).
|
|
203
|
+
|
|
204
|
+
Runs carry a companion `RunMeta@v1` on `runs.meta`, set at `run.start`:
|
|
205
|
+
|
|
206
|
+
```ts
|
|
207
|
+
const h = rig.run.start({
|
|
208
|
+
pipeline: "nightly-report",
|
|
209
|
+
meta: { v: 1, trigger: "cron", actor: "scheduler", config_hash: "…" },
|
|
210
|
+
// pipeline_sha auto-fills from git when you don't set it (never fails the run)
|
|
211
|
+
});
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
### Reserved dimensions
|
|
215
|
+
|
|
216
|
+
Business dimensions live in `tags`. Five keys are **well-known** and become
|
|
217
|
+
first-class group-bys in cost and coverage views — use them verbatim so your data
|
|
218
|
+
lines up with the console's built-ins:
|
|
219
|
+
|
|
220
|
+
| key | meaning |
|
|
221
|
+
|---|---|
|
|
222
|
+
| `subject` | the customer/tenant the work is for — an **opaque** id, hashed if it looks like PII |
|
|
223
|
+
| `feature` | the product feature the call serves |
|
|
224
|
+
| `user` | the end user (an opaque id) |
|
|
225
|
+
| `session` | the conversation/session the call belongs to |
|
|
226
|
+
| `cost_center` | the team/budget to attribute spend to |
|
|
227
|
+
|
|
228
|
+
`modelrig validate` warns on near-misses — a declared `rig.yaml` dimension named
|
|
229
|
+
`customer`, `tenant`, `user_id`, `team`, … is nudged toward the reserved key it
|
|
230
|
+
resembles. They stay ordinary tags; nothing is required.
|
|
231
|
+
|
|
185
232
|
## CLI
|
|
186
233
|
|
|
187
234
|
The package ships a `modelrig` binary:
|
|
@@ -217,8 +264,11 @@ on the quickstart:
|
|
|
217
264
|
- **`modelrig serve` needs the `modelrig-server` package** — it is not bundled
|
|
218
265
|
here.
|
|
219
266
|
- **What the ladder does and does not do.** Fall-through is structural and
|
|
220
|
-
infrastructural,
|
|
221
|
-
|
|
267
|
+
infrastructural, plus an OPTIONAL SDK-lane `qualityGate` predicate you supply
|
|
268
|
+
(a schema-valid output it rejects becomes
|
|
269
|
+
`quality_rejected`); the route-declared
|
|
270
|
+
hosted judge is not built yet, and the full boundary (raw-lane structure, latency
|
|
271
|
+
ordering…) is generated and tripwire-tested on
|
|
222
272
|
[Routing & reliability](https://modelrig.dev/routing-reliability.html#10-not-yet-implemented-the-boundary).
|
|
223
273
|
|
|
224
274
|
## License
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"anthropic.d.ts","sourceRoot":"","sources":["../../src/adapters/anthropic.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAGH,OAAO,KAAK,EAIV,cAAc,EACd,eAAe,EAEhB,MAAM,mBAAmB,CAAC;AAE3B,OAAO,KAAK,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAEhE,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"anthropic.d.ts","sourceRoot":"","sources":["../../src/adapters/anthropic.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAGH,OAAO,KAAK,EAIV,cAAc,EACd,eAAe,EAEhB,MAAM,mBAAmB,CAAC;AAE3B,OAAO,KAAK,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAEhE,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,gBAAgB,CAAC;AAK9D,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AASD;yDACyD;AACzD,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAEhE;AAED,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAclF;AAED,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,MAAM,GAAG,iBAAiB,CAwB3E;AAED,UAAU,aAAa;IACrB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,sDAAsD;IACtD,QAAQ,CAAC,MAAM,CAAC,EAAE,IAAI,CAAC;CACxB;AAED;;mEAEmE;AACnE,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,aAAa,CAAC,EAAE;QAAE,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAA;KAAE,CAAC;CACzD;AAED,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B;wFACoF;IACpF,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB;+DAC2D;IAC3D,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,oBAAoB,EAAE,CAAC;IAC3D,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAC;QAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACtF,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC;IACtB,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,aAAa,EAAE,CAAC;IAC1C,QAAQ,CAAC,WAAW,CAAC,EAAE;QAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;KAAE,GAAG,uBAAuB,CAAC;CACnG;AAED,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,MAAM,EACb,GAAG,EAAE,YAAY,EACjB,MAAM,UAAQ,GACb,qBAAqB,CAuDvB;AAED;iEACiE;AACjE,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,uBAAuB,CAAC,EAAE,MAAM,CAAC;IAC1C,QAAQ,CAAC,2BAA2B,CAAC,EAAE,MAAM,CAAC;IAC9C,QAAQ,CAAC,cAAc,CAAC,EAAE;QACxB,QAAQ,CAAC,yBAAyB,CAAC,EAAE,MAAM,CAAC;QAC5C,QAAQ,CAAC,yBAAyB,CAAC,EAAE,MAAM,CAAC;KAC7C,CAAC;CACH;AAED,0EAA0E;AAC1E,MAAM,WAAW,6BAA6B;IAC5C,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC;CACrC;AAED;;;;;;GAMG;AACH,wBAAgB,4BAA4B,CAC1C,CAAC,EAAE,sBAAsB,GACxB,6BAA6B,CAa/B;AAED,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,sBAAsB,GAAG,eAAe,CAsOtF"}
|
|
@@ -22,6 +22,7 @@ exports.normalizeAnthropicInputUsage = normalizeAnthropicInputUsage;
|
|
|
22
22
|
exports.createAnthropicAdapter = createAnthropicAdapter;
|
|
23
23
|
const sdk_1 = __importDefault(require("@anthropic-ai/sdk"));
|
|
24
24
|
const tool_mapping_1 = require("./tool-mapping");
|
|
25
|
+
const attempt_meta_map_1 = require("./attempt-meta-map");
|
|
25
26
|
const sampling_1 = require("./sampling");
|
|
26
27
|
const DEFAULT_MAX_TOKENS = 32000;
|
|
27
28
|
const OUTPUT_TOOL_NAME = "route_output";
|
|
@@ -159,6 +160,8 @@ function createAnthropicAdapter(config) {
|
|
|
159
160
|
supportsTools: () => true,
|
|
160
161
|
async call(req) {
|
|
161
162
|
const model = req.candidate.model;
|
|
163
|
+
// WI-1: native attempt envelope for this call (see openai.ts for the pattern).
|
|
164
|
+
let attemptMeta;
|
|
162
165
|
// F1: post-stream failures still billed tokens — attach usage.
|
|
163
166
|
const fail = (cls, message, fixHint, usage) => ({
|
|
164
167
|
ok: false,
|
|
@@ -170,6 +173,7 @@ function createAnthropicAdapter(config) {
|
|
|
170
173
|
...(fixHint !== undefined ? { fixHint } : {}),
|
|
171
174
|
},
|
|
172
175
|
...(usage !== undefined ? { usage } : {}),
|
|
176
|
+
...(attemptMeta !== undefined ? { meta: attemptMeta } : {}),
|
|
173
177
|
});
|
|
174
178
|
const wantJson = req.outputSchema !== null;
|
|
175
179
|
try {
|
|
@@ -210,9 +214,20 @@ function createAnthropicAdapter(config) {
|
|
|
210
214
|
};
|
|
211
215
|
let completionTokens = 0;
|
|
212
216
|
let stopReason = null;
|
|
217
|
+
// WI-1 native fields: message.id + message.model arrive at message_start.
|
|
218
|
+
let responseModel = "";
|
|
219
|
+
let responseId = "";
|
|
213
220
|
for await (const event of events) {
|
|
214
|
-
if (event.type === "message_start"
|
|
215
|
-
|
|
221
|
+
if (event.type === "message_start") {
|
|
222
|
+
if (event.message?.usage !== undefined) {
|
|
223
|
+
inputUsage = normalizeAnthropicInputUsage(event.message.usage);
|
|
224
|
+
}
|
|
225
|
+
if (responseModel === "" && typeof event.message?.model === "string") {
|
|
226
|
+
responseModel = event.message.model;
|
|
227
|
+
}
|
|
228
|
+
if (responseId === "" && typeof event.message?.id === "string") {
|
|
229
|
+
responseId = event.message.id;
|
|
230
|
+
}
|
|
216
231
|
}
|
|
217
232
|
else if (event.type === "content_block_start" &&
|
|
218
233
|
wantTools &&
|
|
@@ -257,13 +272,25 @@ function createAnthropicAdapter(config) {
|
|
|
257
272
|
};
|
|
258
273
|
// Tool-call turn (spec §4.4): `arguments` is the raw JSON string the
|
|
259
274
|
// model streamed (OpenAI parity) — text may be empty, skip the guards.
|
|
260
|
-
|
|
261
|
-
|
|
275
|
+
const toolCalls = wantTools && toolBlocks.size > 0
|
|
276
|
+
? [...toolBlocks.values()].map((b, i) => ({
|
|
262
277
|
id: b.id !== "" ? b.id : `call_${i}`,
|
|
263
278
|
type: "function",
|
|
264
279
|
function: { name: b.name, arguments: b.json === "" ? "{}" : b.json },
|
|
265
|
-
}))
|
|
266
|
-
|
|
280
|
+
}))
|
|
281
|
+
: undefined;
|
|
282
|
+
// WI-1: native attempt envelope. refusal derives from stop_reason=refusal.
|
|
283
|
+
attemptMeta = (0, attempt_meta_map_1.buildAdapterMeta)("anthropic", {
|
|
284
|
+
req,
|
|
285
|
+
finishReasonRaw: stopReason,
|
|
286
|
+
refusal: stopReason === "refusal",
|
|
287
|
+
modelSnapshot: responseModel,
|
|
288
|
+
providerRequestId: responseId,
|
|
289
|
+
toolCalls,
|
|
290
|
+
streaming: false,
|
|
291
|
+
});
|
|
292
|
+
if (toolCalls !== undefined) {
|
|
293
|
+
return { ok: true, text, usage, toolCalls, meta: attemptMeta };
|
|
267
294
|
}
|
|
268
295
|
if (stopReason === "refusal") {
|
|
269
296
|
return fail("refusal", "Anthropic refused this request (stop_reason=refusal)", undefined, usage);
|
|
@@ -274,11 +301,12 @@ function createAnthropicAdapter(config) {
|
|
|
274
301
|
if (stopReason === "max_tokens" && wantJson) {
|
|
275
302
|
return fail("content_invalid", "Anthropic structured output truncated (stop_reason=max_tokens)", "raise maxOutputTokens for this route", usage);
|
|
276
303
|
}
|
|
277
|
-
return { ok: true, text, usage };
|
|
304
|
+
return { ok: true, text, usage, meta: attemptMeta };
|
|
278
305
|
}
|
|
279
306
|
catch (error) {
|
|
280
307
|
const message = error instanceof Error ? error.message : String(error);
|
|
281
308
|
const { cls, fixHint } = classifyAnthropicFailure(message);
|
|
309
|
+
attemptMeta = (0, attempt_meta_map_1.transportErrorMeta)(error);
|
|
282
310
|
return {
|
|
283
311
|
ok: false,
|
|
284
312
|
failure: {
|
|
@@ -289,6 +317,7 @@ function createAnthropicAdapter(config) {
|
|
|
289
317
|
cause: error,
|
|
290
318
|
...(fixHint !== undefined ? { fixHint } : {}),
|
|
291
319
|
},
|
|
320
|
+
meta: attemptMeta,
|
|
292
321
|
};
|
|
293
322
|
}
|
|
294
323
|
},
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-adapter NATIVE metadata mapping (WI-1, spec §2). ONE place that turns a
|
|
3
|
+
* provider's native finish/refusal/usage/id signals into the additive
|
|
4
|
+
* `AdapterResult.meta` (`Partial<AttemptMetaV1>`). Adapters map ONLY what the
|
|
5
|
+
* provider reports; the producer (`telemetry/attempt-meta.ts`) adds
|
|
6
|
+
* hashes/sizes/cost and enforces the classification rule. Absent = unknown;
|
|
7
|
+
* nothing here is inferred.
|
|
8
|
+
*
|
|
9
|
+
* `refusal` derivation, per adapter:
|
|
10
|
+
* - openai / grok / deepseek / deepinfra / fireworks (OpenAI dialect): the
|
|
11
|
+
* stream carried a non-empty `choices[].delta.refusal` string, OR
|
|
12
|
+
* `finish_reason === "content_filter"`.
|
|
13
|
+
* - anthropic: `stop_reason === "refusal"`.
|
|
14
|
+
* - gemini: `finishReason` is `SAFETY` / `PROHIBITED_CONTENT` / `RECITATION`.
|
|
15
|
+
* The refusal TEXT itself is never carried (content) — only the boolean.
|
|
16
|
+
*/
|
|
17
|
+
import type { AttemptMetaV1, FinishReason } from "../telemetry/attempt-meta";
|
|
18
|
+
import type { AdapterRequest, ProviderId, ToolCall } from "../types/provider";
|
|
19
|
+
/** Map a provider's native finish/stop reason to the `FinishReason` enum
|
|
20
|
+
* (spec §2). Unknown/absent ⇒ undefined (never inferred); an unrecognised
|
|
21
|
+
* non-empty value ⇒ `"other"`. */
|
|
22
|
+
export declare function mapFinishReason(provider: ProviderId, raw: string | null | undefined): FinishReason | undefined;
|
|
23
|
+
/** The declared sampling echo (spec §2 `sampling`) — the values ModelRig sent,
|
|
24
|
+
* read back from the request. topP → top_p, maxOutputTokens → max_output_tokens
|
|
25
|
+
* (the raw lane's top-level or the routed lane's `sampling` block). Numbers
|
|
26
|
+
* only; undefined when the caller declared none. */
|
|
27
|
+
export declare function samplingEcho(req: AdapterRequest): {
|
|
28
|
+
temperature?: number;
|
|
29
|
+
top_p?: number;
|
|
30
|
+
max_output_tokens?: number;
|
|
31
|
+
} | undefined;
|
|
32
|
+
/** Aggregate normalized tool calls into `{ name, count }` (RAW names — the
|
|
33
|
+
* producer hashes undeclared ones). Undefined when the turn made none. */
|
|
34
|
+
export declare function aggregateToolCalls(toolCalls: readonly ToolCall[] | undefined): {
|
|
35
|
+
name: string;
|
|
36
|
+
count: number;
|
|
37
|
+
}[] | undefined;
|
|
38
|
+
/** The signals an adapter collects on ONE completed call. All optional. */
|
|
39
|
+
export interface AdapterMetaSignals {
|
|
40
|
+
readonly req: AdapterRequest;
|
|
41
|
+
readonly finishReasonRaw?: string | null;
|
|
42
|
+
readonly refusal?: boolean;
|
|
43
|
+
readonly reasoningTokens?: number;
|
|
44
|
+
readonly modelSnapshot?: string;
|
|
45
|
+
readonly providerRequestId?: string;
|
|
46
|
+
readonly region?: string;
|
|
47
|
+
readonly toolCalls?: readonly ToolCall[];
|
|
48
|
+
readonly grounding?: {
|
|
49
|
+
citations?: number;
|
|
50
|
+
searchProvider?: string;
|
|
51
|
+
};
|
|
52
|
+
/** true = the run loop streamed deltas (stream()); false/absent = buffered. */
|
|
53
|
+
readonly streaming?: boolean;
|
|
54
|
+
}
|
|
55
|
+
/** Build one adapter's NATIVE `meta` (spec §2). Only defined signals appear —
|
|
56
|
+
* everything else is unknown and omitted. The producer takes it from here. */
|
|
57
|
+
export declare function buildAdapterMeta(provider: ProviderId, s: AdapterMetaSignals): Partial<AttemptMetaV1>;
|
|
58
|
+
/** A transport/SDK error's coarse envelope (spec §2 `http_status`/`error_code`).
|
|
59
|
+
* Reads `error.status` (number) and a short `error.code` / `error.error.code`
|
|
60
|
+
* slug — NEVER the message (that is free text). The producer's classification
|
|
61
|
+
* rule drops a code that turns out to be free text. */
|
|
62
|
+
export declare function transportErrorMeta(error: unknown): Partial<AttemptMetaV1>;
|
|
63
|
+
//# sourceMappingURL=attempt-meta-map.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"attempt-meta-map.d.ts","sourceRoot":"","sources":["../../src/adapters/attempt-meta-map.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAC7E,OAAO,KAAK,EAAE,cAAc,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAE9E;;kCAEkC;AAClC,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,UAAU,EACpB,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAC7B,YAAY,GAAG,SAAS,CA6C1B;AAED;;;oDAGoD;AACpD,wBAAgB,YAAY,CAC1B,GAAG,EAAE,cAAc,GAClB;IAAE,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,iBAAiB,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAOlF;AAED;0EAC0E;AAC1E,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,SAAS,QAAQ,EAAE,GAAG,SAAS,GACzC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,EAAE,GAAG,SAAS,CAQ/C;AAED,2EAA2E;AAC3E,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,GAAG,EAAE,cAAc,CAAC;IAC7B,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzC,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC;IACpC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,QAAQ,EAAE,CAAC;IACzC,QAAQ,CAAC,SAAS,CAAC,EAAE;QAAE,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,cAAc,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACrE,+EAA+E;IAC/E,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC;CAC9B;AAED;8EAC8E;AAC9E,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,UAAU,EACpB,CAAC,EAAE,kBAAkB,GACpB,OAAO,CAAC,aAAa,CAAC,CAyBxB;AAED;;;uDAGuD;AACvD,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,aAAa,CAAC,CASzE"}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Per-adapter NATIVE metadata mapping (WI-1, spec §2). ONE place that turns a
|
|
4
|
+
* provider's native finish/refusal/usage/id signals into the additive
|
|
5
|
+
* `AdapterResult.meta` (`Partial<AttemptMetaV1>`). Adapters map ONLY what the
|
|
6
|
+
* provider reports; the producer (`telemetry/attempt-meta.ts`) adds
|
|
7
|
+
* hashes/sizes/cost and enforces the classification rule. Absent = unknown;
|
|
8
|
+
* nothing here is inferred.
|
|
9
|
+
*
|
|
10
|
+
* `refusal` derivation, per adapter:
|
|
11
|
+
* - openai / grok / deepseek / deepinfra / fireworks (OpenAI dialect): the
|
|
12
|
+
* stream carried a non-empty `choices[].delta.refusal` string, OR
|
|
13
|
+
* `finish_reason === "content_filter"`.
|
|
14
|
+
* - anthropic: `stop_reason === "refusal"`.
|
|
15
|
+
* - gemini: `finishReason` is `SAFETY` / `PROHIBITED_CONTENT` / `RECITATION`.
|
|
16
|
+
* The refusal TEXT itself is never carried (content) — only the boolean.
|
|
17
|
+
*/
|
|
18
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
|
+
exports.mapFinishReason = mapFinishReason;
|
|
20
|
+
exports.samplingEcho = samplingEcho;
|
|
21
|
+
exports.aggregateToolCalls = aggregateToolCalls;
|
|
22
|
+
exports.buildAdapterMeta = buildAdapterMeta;
|
|
23
|
+
exports.transportErrorMeta = transportErrorMeta;
|
|
24
|
+
/** Map a provider's native finish/stop reason to the `FinishReason` enum
|
|
25
|
+
* (spec §2). Unknown/absent ⇒ undefined (never inferred); an unrecognised
|
|
26
|
+
* non-empty value ⇒ `"other"`. */
|
|
27
|
+
function mapFinishReason(provider, raw) {
|
|
28
|
+
if (raw === null || raw === undefined || raw === "")
|
|
29
|
+
return undefined;
|
|
30
|
+
if (provider === "anthropic") {
|
|
31
|
+
switch (raw) {
|
|
32
|
+
case "end_turn":
|
|
33
|
+
case "stop_sequence":
|
|
34
|
+
return "stop";
|
|
35
|
+
case "max_tokens":
|
|
36
|
+
return "length";
|
|
37
|
+
case "tool_use":
|
|
38
|
+
return "tool_calls";
|
|
39
|
+
case "refusal":
|
|
40
|
+
return "refusal";
|
|
41
|
+
default:
|
|
42
|
+
return "other";
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
if (provider === "gemini") {
|
|
46
|
+
switch (raw) {
|
|
47
|
+
case "STOP":
|
|
48
|
+
return "stop";
|
|
49
|
+
case "MAX_TOKENS":
|
|
50
|
+
return "length";
|
|
51
|
+
case "SAFETY":
|
|
52
|
+
case "PROHIBITED_CONTENT":
|
|
53
|
+
case "RECITATION":
|
|
54
|
+
return "content_filter";
|
|
55
|
+
default:
|
|
56
|
+
return "other";
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
// OpenAI dialect (openai / grok / deepseek / deepinfra / fireworks).
|
|
60
|
+
switch (raw) {
|
|
61
|
+
case "stop":
|
|
62
|
+
return "stop";
|
|
63
|
+
case "length":
|
|
64
|
+
return "length";
|
|
65
|
+
case "tool_calls":
|
|
66
|
+
case "function_call":
|
|
67
|
+
return "tool_calls";
|
|
68
|
+
case "content_filter":
|
|
69
|
+
return "content_filter";
|
|
70
|
+
default:
|
|
71
|
+
return "other";
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/** The declared sampling echo (spec §2 `sampling`) — the values ModelRig sent,
|
|
75
|
+
* read back from the request. topP → top_p, maxOutputTokens → max_output_tokens
|
|
76
|
+
* (the raw lane's top-level or the routed lane's `sampling` block). Numbers
|
|
77
|
+
* only; undefined when the caller declared none. */
|
|
78
|
+
function samplingEcho(req) {
|
|
79
|
+
const s = {};
|
|
80
|
+
if (typeof req.sampling?.temperature === "number")
|
|
81
|
+
s.temperature = req.sampling.temperature;
|
|
82
|
+
if (typeof req.sampling?.topP === "number")
|
|
83
|
+
s.top_p = req.sampling.topP;
|
|
84
|
+
const maxOut = req.sampling?.maxOutputTokens ?? req.maxOutputTokens;
|
|
85
|
+
if (typeof maxOut === "number")
|
|
86
|
+
s.max_output_tokens = maxOut;
|
|
87
|
+
return Object.keys(s).length > 0 ? s : undefined;
|
|
88
|
+
}
|
|
89
|
+
/** Aggregate normalized tool calls into `{ name, count }` (RAW names — the
|
|
90
|
+
* producer hashes undeclared ones). Undefined when the turn made none. */
|
|
91
|
+
function aggregateToolCalls(toolCalls) {
|
|
92
|
+
if (toolCalls === undefined || toolCalls.length === 0)
|
|
93
|
+
return undefined;
|
|
94
|
+
const counts = new Map();
|
|
95
|
+
for (const tc of toolCalls) {
|
|
96
|
+
const name = tc.function.name;
|
|
97
|
+
counts.set(name, (counts.get(name) ?? 0) + 1);
|
|
98
|
+
}
|
|
99
|
+
return [...counts.entries()].map(([name, count]) => ({ name, count }));
|
|
100
|
+
}
|
|
101
|
+
/** Build one adapter's NATIVE `meta` (spec §2). Only defined signals appear —
|
|
102
|
+
* everything else is unknown and omitted. The producer takes it from here. */
|
|
103
|
+
function buildAdapterMeta(provider, s) {
|
|
104
|
+
const meta = {};
|
|
105
|
+
const finish = mapFinishReason(provider, s.finishReasonRaw);
|
|
106
|
+
if (finish !== undefined)
|
|
107
|
+
meta.finish_reason = finish;
|
|
108
|
+
if (typeof s.refusal === "boolean")
|
|
109
|
+
meta.refusal = s.refusal;
|
|
110
|
+
if (typeof s.reasoningTokens === "number" && s.reasoningTokens > 0) {
|
|
111
|
+
meta.reasoning_tokens = s.reasoningTokens;
|
|
112
|
+
}
|
|
113
|
+
if (typeof s.modelSnapshot === "string" && s.modelSnapshot !== "")
|
|
114
|
+
meta.model_snapshot = s.modelSnapshot;
|
|
115
|
+
if (typeof s.providerRequestId === "string" && s.providerRequestId !== "") {
|
|
116
|
+
meta.provider_request_id = s.providerRequestId;
|
|
117
|
+
}
|
|
118
|
+
if (typeof s.region === "string" && s.region !== "")
|
|
119
|
+
meta.region = s.region;
|
|
120
|
+
const sampling = samplingEcho(s.req);
|
|
121
|
+
if (sampling !== undefined)
|
|
122
|
+
meta.sampling = sampling;
|
|
123
|
+
const tools = aggregateToolCalls(s.toolCalls);
|
|
124
|
+
if (tools !== undefined)
|
|
125
|
+
meta.tool_calls = tools;
|
|
126
|
+
if (s.grounding !== undefined) {
|
|
127
|
+
const g = {};
|
|
128
|
+
if (typeof s.grounding.citations === "number")
|
|
129
|
+
g.citations = s.grounding.citations;
|
|
130
|
+
if (typeof s.grounding.searchProvider === "string")
|
|
131
|
+
g.search_provider = s.grounding.searchProvider;
|
|
132
|
+
if (Object.keys(g).length > 0)
|
|
133
|
+
meta.grounding = g;
|
|
134
|
+
}
|
|
135
|
+
if (typeof s.streaming === "boolean")
|
|
136
|
+
meta.streaming = s.streaming;
|
|
137
|
+
return meta;
|
|
138
|
+
}
|
|
139
|
+
/** A transport/SDK error's coarse envelope (spec §2 `http_status`/`error_code`).
|
|
140
|
+
* Reads `error.status` (number) and a short `error.code` / `error.error.code`
|
|
141
|
+
* slug — NEVER the message (that is free text). The producer's classification
|
|
142
|
+
* rule drops a code that turns out to be free text. */
|
|
143
|
+
function transportErrorMeta(error) {
|
|
144
|
+
const meta = {};
|
|
145
|
+
if (typeof error === "object" && error !== null) {
|
|
146
|
+
const e = error;
|
|
147
|
+
if (typeof e.status === "number" && Number.isFinite(e.status))
|
|
148
|
+
meta.http_status = e.status;
|
|
149
|
+
const code = typeof e.code === "string" ? e.code : e.error?.code;
|
|
150
|
+
if (typeof code === "string" && code !== "")
|
|
151
|
+
meta.error_code = code;
|
|
152
|
+
}
|
|
153
|
+
return meta;
|
|
154
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"deepseek.d.ts","sourceRoot":"","sources":["../../src/adapters/deepseek.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAGH,OAAO,KAAK,EAIV,cAAc,EACd,eAAe,EAChB,MAAM,mBAAmB,CAAC;AAE3B,OAAO,KAAK,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAMhE,OAAO,KAAK,EAAuB,cAAc,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"deepseek.d.ts","sourceRoot":"","sources":["../../src/adapters/deepseek.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAGH,OAAO,KAAK,EAIV,cAAc,EACd,eAAe,EAChB,MAAM,mBAAmB,CAAC;AAE3B,OAAO,KAAK,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAMhE,OAAO,KAAK,EAAuB,cAAc,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAQhG,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAKD,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,cAAc,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAMhF;AAED,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,MAAM,GAAG,iBAAiB,CAiB1E;AAED,UAAU,WAAW;IACnB,QAAQ,CAAC,IAAI,EAAE,QAAQ,GAAG,MAAM,CAAC;IACjC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,QAAQ,EAAE,SAAS,WAAW,EAAE,CAAC;IAC1C,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC;IACtB,QAAQ,CAAC,cAAc,EAAE;QAAE,QAAQ,CAAC,aAAa,EAAE,IAAI,CAAA;KAAE,CAAC;IAC1D,8EAA8E;IAC9E,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,eAAe,CAAC,EAAE;QAAE,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAA;KAAE,CAAC;IAC5D,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,cAAc,EAAE,CAAC;IAC3C,QAAQ,CAAC,WAAW,CAAC,EAAE,oBAAoB,CAAC;CAC7C;AAOD,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,YAAY,GAAG,oBAAoB,CAkC1F;AAED,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,qBAAqB,GAAG,eAAe,CAoLpF"}
|
|
@@ -24,6 +24,7 @@ exports.createDeepSeekAdapter = createDeepSeekAdapter;
|
|
|
24
24
|
const openai_1 = __importDefault(require("openai"));
|
|
25
25
|
const tool_mapping_1 = require("./tool-mapping");
|
|
26
26
|
const openai_stream_1 = require("./openai-stream");
|
|
27
|
+
const attempt_meta_map_1 = require("./attempt-meta-map");
|
|
27
28
|
const sampling_1 = require("./sampling");
|
|
28
29
|
const DEEPSEEK_BASE_URL = "https://api.deepseek.com";
|
|
29
30
|
const DEFAULT_MAX_TOKENS = 64000; // R1/V3 max — full capacity for long reports (ported)
|
|
@@ -102,6 +103,8 @@ function createDeepSeekAdapter(config) {
|
|
|
102
103
|
supportsTools: () => true,
|
|
103
104
|
async call(req) {
|
|
104
105
|
const model = req.candidate.model;
|
|
106
|
+
// WI-1: native attempt envelope for this call (see openai.ts for the pattern).
|
|
107
|
+
let attemptMeta;
|
|
105
108
|
// F1: post-stream failures still billed tokens — attach usage.
|
|
106
109
|
const fail = (cls, message, fixHint, usage) => ({
|
|
107
110
|
ok: false,
|
|
@@ -113,6 +116,7 @@ function createDeepSeekAdapter(config) {
|
|
|
113
116
|
...(fixHint !== undefined ? { fixHint } : {}),
|
|
114
117
|
},
|
|
115
118
|
...(usage !== undefined ? { usage } : {}),
|
|
119
|
+
...(attemptMeta !== undefined ? { meta: attemptMeta } : {}),
|
|
116
120
|
});
|
|
117
121
|
try {
|
|
118
122
|
const client = new openai_1.default({
|
|
@@ -130,12 +134,18 @@ function createDeepSeekAdapter(config) {
|
|
|
130
134
|
let finishReason = null;
|
|
131
135
|
// TTFB (Phase 4 WS3): first streamed chunk = first body byte.
|
|
132
136
|
let ttfbMs;
|
|
137
|
+
let responseModel = "";
|
|
138
|
+
let responseId = "";
|
|
133
139
|
const toolCallAcc = (0, tool_mapping_1.createOpenAIToolCallAccumulator)();
|
|
134
140
|
// Only delta.content is accumulated — a reasoner's reasoning_content
|
|
135
141
|
// (chain-of-thought) must never leak into the JSON buffer (ported).
|
|
136
142
|
for await (const chunk of stream) {
|
|
137
143
|
if (ttfbMs === undefined)
|
|
138
144
|
ttfbMs = Date.now() - dispatchedAt;
|
|
145
|
+
if (responseModel === "" && typeof chunk.model === "string")
|
|
146
|
+
responseModel = chunk.model;
|
|
147
|
+
if (responseId === "" && typeof chunk.id === "string")
|
|
148
|
+
responseId = chunk.id;
|
|
139
149
|
const choice = chunk.choices[0];
|
|
140
150
|
if (choice?.delta?.content)
|
|
141
151
|
text += choice.delta.content;
|
|
@@ -159,8 +169,19 @@ function createDeepSeekAdapter(config) {
|
|
|
159
169
|
};
|
|
160
170
|
// Tool-call turn (spec §4.4): surface normalized calls, skip content guards.
|
|
161
171
|
const toolCalls = toolCallAcc.finalize();
|
|
172
|
+
// WI-1: native attempt envelope. DeepSeek has no `refusal` delta — a
|
|
173
|
+
// refusal surfaces as finish_reason content_filter.
|
|
174
|
+
attemptMeta = (0, attempt_meta_map_1.buildAdapterMeta)("deepseek", {
|
|
175
|
+
req,
|
|
176
|
+
finishReasonRaw: finishReason,
|
|
177
|
+
refusal: finishReason === "content_filter",
|
|
178
|
+
modelSnapshot: responseModel,
|
|
179
|
+
providerRequestId: responseId,
|
|
180
|
+
toolCalls,
|
|
181
|
+
streaming: false,
|
|
182
|
+
});
|
|
162
183
|
if (toolCalls !== undefined) {
|
|
163
|
-
return { ok: true, text, usage, toolCalls };
|
|
184
|
+
return { ok: true, text, usage, toolCalls, meta: attemptMeta };
|
|
164
185
|
}
|
|
165
186
|
if (text.trim().length === 0) {
|
|
166
187
|
return fail("content_invalid", `empty response from DeepSeek (finish_reason=${finishReason ?? "none"})`, undefined, usage);
|
|
@@ -168,11 +189,12 @@ function createDeepSeekAdapter(config) {
|
|
|
168
189
|
if (finishReason === "length" && req.outputSchema !== null) {
|
|
169
190
|
return fail("content_invalid", "DeepSeek JSON output truncated (finish_reason=length)", "raise maxOutputTokens for this route", usage);
|
|
170
191
|
}
|
|
171
|
-
return { ok: true, text, usage };
|
|
192
|
+
return { ok: true, text, usage, meta: attemptMeta };
|
|
172
193
|
}
|
|
173
194
|
catch (error) {
|
|
174
195
|
const message = error instanceof Error ? error.message : String(error);
|
|
175
196
|
const { cls, fixHint } = classifyDeepSeekFailure(message);
|
|
197
|
+
attemptMeta = (0, attempt_meta_map_1.transportErrorMeta)(error);
|
|
176
198
|
return {
|
|
177
199
|
ok: false,
|
|
178
200
|
failure: {
|
|
@@ -183,6 +205,7 @@ function createDeepSeekAdapter(config) {
|
|
|
183
205
|
cause: error,
|
|
184
206
|
...(fixHint !== undefined ? { fixHint } : {}),
|
|
185
207
|
},
|
|
208
|
+
meta: attemptMeta,
|
|
186
209
|
};
|
|
187
210
|
}
|
|
188
211
|
},
|