auto-model-router 0.24.0 → 0.25.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/.omp-plugin/marketplace.json +2 -2
- package/package.json +1 -1
- package/src/router/candidates.ts +20 -12
- package/test/tier-plan.test.ts +16 -0
|
@@ -7,14 +7,14 @@
|
|
|
7
7
|
},
|
|
8
8
|
"metadata": {
|
|
9
9
|
"description": "auto-model-router: a local cost/complexity-aware model router for Oh My Pi, backed by OpenRouter",
|
|
10
|
-
"version": "0.
|
|
10
|
+
"version": "0.25.0",
|
|
11
11
|
"pluginRoot": "."
|
|
12
12
|
},
|
|
13
13
|
"plugins": [
|
|
14
14
|
{
|
|
15
15
|
"name": "auto-model-router",
|
|
16
16
|
"description": "Local cost/complexity-aware model router for Oh My Pi, backed by OpenRouter. Runs in-process, routes per turn by price and task complexity, with budget caps, mid-stream escalation, and cache-aware hysteresis.",
|
|
17
|
-
"version": "0.
|
|
17
|
+
"version": "0.25.0",
|
|
18
18
|
"author": {
|
|
19
19
|
"name": "drewappling",
|
|
20
20
|
"email": "drewappling@gmail.com"
|
package/package.json
CHANGED
package/src/router/candidates.ts
CHANGED
|
@@ -254,20 +254,33 @@ export function buildCandidates(args: BuildCandidatesArgs): { candidates: Candid
|
|
|
254
254
|
// Price ceilings at the ACTUAL prompt size: long-context overrides can
|
|
255
255
|
// push a model over the ceiling exactly when conversations get long.
|
|
256
256
|
// Catalog prices are per-token; ceilings are per million tokens.
|
|
257
|
+
//
|
|
258
|
+
// The ceiling is compared against the BIASED price, because that is what the turn
|
|
259
|
+
// actually costs this deployment: capacity already paid for — an Ollama plan's
|
|
260
|
+
// included credits, a Claude Pro/Max subscription — carries the list price of the
|
|
261
|
+
// twin it is priced from, and a subscription model at $2-5/Mtok list would be
|
|
262
|
+
// thrown out here on every cheap tier before `costBias` was ever consulted. That
|
|
263
|
+
// made the bias silently inert: setting it to 0.00001 changed no decision at all.
|
|
257
264
|
const price = priceAt(model, Math.max(1, features.promptTokens));
|
|
258
|
-
|
|
265
|
+
const providerBias =
|
|
266
|
+
snapshot.providerBias?.[model.provider] ??
|
|
267
|
+
(model.provider === "ollama" ? cfg.ollama.costBias : (cfg.upstreams.find((u) => u.id === model.provider)?.costBias ?? 1));
|
|
268
|
+
const biasedPrompt = price.prompt * providerBias;
|
|
269
|
+
const biasedCompletion = price.completion * providerBias;
|
|
270
|
+
const biasNote = providerBias === 1 ? "" : ` (×${providerBias} bias on $${(price.prompt * 1e6).toFixed(2)} list)`;
|
|
271
|
+
if (!relaxPrice && priceCeiling !== undefined && biasedPrompt * 1e6 > priceCeiling) {
|
|
259
272
|
rejected.push({
|
|
260
273
|
slug,
|
|
261
274
|
reason: "over_price_ceiling",
|
|
262
|
-
detail: `input $${(
|
|
275
|
+
detail: `input $${(biasedPrompt * 1e6).toFixed(2)}/Mtok > ceiling $${priceCeiling.toFixed(2)}${biasNote}`,
|
|
263
276
|
});
|
|
264
277
|
continue;
|
|
265
278
|
}
|
|
266
|
-
if (!relaxPrice && tierCfg.maxOutputPerMtok !== undefined &&
|
|
279
|
+
if (!relaxPrice && tierCfg.maxOutputPerMtok !== undefined && biasedCompletion * 1e6 > tierCfg.maxOutputPerMtok) {
|
|
267
280
|
rejected.push({
|
|
268
281
|
slug,
|
|
269
282
|
reason: "over_price_ceiling",
|
|
270
|
-
detail: `output $${(
|
|
283
|
+
detail: `output $${(biasedCompletion * 1e6).toFixed(2)}/Mtok > ceiling $${tierCfg.maxOutputPerMtok}${biasNote}`,
|
|
271
284
|
});
|
|
272
285
|
continue;
|
|
273
286
|
}
|
|
@@ -357,14 +370,9 @@ export function buildCandidates(args: BuildCandidatesArgs): { candidates: Candid
|
|
|
357
370
|
filters.escalationCostWeight > 0 && args.escalationUsdPerPromptToken !== undefined
|
|
358
371
|
? filters.escalationCostWeight * escalationRate * args.escalationUsdPerPromptToken * features.promptTokens
|
|
359
372
|
: 0;
|
|
360
|
-
//
|
|
361
|
-
//
|
|
362
|
-
//
|
|
363
|
-
// The snapshot carries the LIVE bias (credit-aware, and each named upstream's own);
|
|
364
|
-
// the static config value is the fallback for snapshots built without one.
|
|
365
|
-
const providerBias =
|
|
366
|
-
snapshot.providerBias?.[model.provider] ??
|
|
367
|
-
(model.provider === "ollama" ? cfg.ollama.costBias : (cfg.upstreams.find((u) => u.id === model.provider)?.costBias ?? 1));
|
|
373
|
+
// `providerBias` is computed with the price ceilings above — capacity already paid for
|
|
374
|
+
// is money already spent, so it is valued below list in ranking AND against the
|
|
375
|
+
// ceilings. The ledger still records list price either way.
|
|
368
376
|
const effectiveUsd = (fc.expectedUsd / Math.max(trustScore, 0.5) + escalationUsd) * latencyMult * providerBias;
|
|
369
377
|
// Score is assigned in a SECOND PASS below: both qualityNormalization and
|
|
370
378
|
// capabilityFloorUsd are properties of the candidate SET, not of one
|
package/test/tier-plan.test.ts
CHANGED
|
@@ -358,6 +358,22 @@ describe("adaptive price ceilings", () => {
|
|
|
358
358
|
expect(on.candidates.map((c) => c.model.slug)).not.toContain("a/4");
|
|
359
359
|
expect(on.rejected.some((r) => r.slug === "a/4" && r.reason === "over_price_ceiling")).toBe(true);
|
|
360
360
|
});
|
|
361
|
+
|
|
362
|
+
test("a price ceiling judges the BIASED price, so prepaid capacity is not thrown out on list", () => {
|
|
363
|
+
// A subscription upstream inherits its OpenRouter twin's list price ($4/Mtok here) and is
|
|
364
|
+
// discounted by `costBias` because the capacity is already paid for. Judging the ceiling on
|
|
365
|
+
// list threw it out before the bias was ever read, which made the bias entirely inert.
|
|
366
|
+
const snap = snapshot(priced);
|
|
367
|
+
const biased = { ...snap, providerBias: { [snap.models[0]!.provider]: 0.1 } };
|
|
368
|
+
const run = (s: typeof snap) =>
|
|
369
|
+
buildCandidates({ req, features, tier: "moderate", task: "coding", snapshot: s, ledger: null, cfg: { ...BASE, adaptivePriceCeilings: true }, expectedCompletionTokens: 512, warmSlug: null });
|
|
370
|
+
// Unbiased: the band tightens moderate to $3 and a/4 is over it.
|
|
371
|
+
expect(run(snap).rejected.some((r) => r.slug === "a/4" && r.reason === "over_price_ceiling")).toBe(true);
|
|
372
|
+
// Biased ×0.1: $4 list is $0.40 to this deployment, so it clears the same ceiling.
|
|
373
|
+
const on = run(biased);
|
|
374
|
+
expect(on.candidates.map((c) => c.model.slug)).toContain("a/4");
|
|
375
|
+
expect(on.rejected.some((r) => r.slug === "a/4")).toBe(false);
|
|
376
|
+
});
|
|
361
377
|
});
|
|
362
378
|
|
|
363
379
|
describe("quality normalization and capability floor (benchmark findings 4/6)", () => {
|