auto-model-router 0.27.0 → 0.28.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/catalog/ollama-catalog.ts +16 -7
- package/src/server/http.ts +22 -2
- package/test/ollama.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.28.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.28.0",
|
|
18
18
|
"author": {
|
|
19
19
|
"name": "drewappling",
|
|
20
20
|
"email": "drewappling@gmail.com"
|
package/package.json
CHANGED
|
@@ -125,16 +125,22 @@ export function buildOllamaModels(args: BuildOllamaArgs): CatalogModel[] {
|
|
|
125
125
|
for (const l of listings) {
|
|
126
126
|
if (!l.isCloud && !cfg.includeLocal) continue;
|
|
127
127
|
const priceName = l.remoteModel ?? l.id;
|
|
128
|
-
const rate = ollamaRateFor(priceName, cfg.prices);
|
|
129
|
-
if (rate === null) {
|
|
130
|
-
skipped.push(l.id);
|
|
131
|
-
continue;
|
|
132
|
-
}
|
|
133
128
|
// A pin may name the tagged cloud name, its base, or the listed id.
|
|
134
129
|
const bare = bareCloudName(priceName);
|
|
135
130
|
const base = bare.includes(":") ? bare.slice(0, bare.indexOf(":")) : bare;
|
|
136
131
|
const pinned = cfg.twins[bare] ?? cfg.twins[base] ?? cfg.twins[l.id];
|
|
137
132
|
const twin = (pinned !== undefined ? bySlug.get(pinned) : undefined) ?? twins.get(ollamaTwinKey(priceName)) ?? null;
|
|
133
|
+
// Ollama publishes no prices, so they come from a static table — which means a model
|
|
134
|
+
// Ollama ships today is INVISIBLE until that table gains an entry, however good it is.
|
|
135
|
+
// Measured: deepseek-v4.1-flash was listed by /api/tags and dropped here, so routing
|
|
136
|
+
// never saw it. The OpenRouter twin sells the same weights per token, so it is the
|
|
137
|
+
// honest stand-in until the table catches up; a model with neither is still skipped.
|
|
138
|
+
const rate = ollamaRateFor(priceName, cfg.prices);
|
|
139
|
+
const price = rate !== null ? toPrice(rate.rate) : twin !== null ? { ...twin.price } : null;
|
|
140
|
+
if (price === null) {
|
|
141
|
+
skipped.push(`${l.id} (no price and no twin)`);
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
138
144
|
const contextLength = l.contextLength ?? twin?.contextLength ?? null;
|
|
139
145
|
if (contextLength === null) {
|
|
140
146
|
skipped.push(`${l.id} (no context length)`);
|
|
@@ -156,7 +162,7 @@ export function buildOllamaModels(args: BuildOllamaArgs): CatalogModel[] {
|
|
|
156
162
|
// Ollama's OpenAI-compatible endpoint documents `tool_choice` as unsupported.
|
|
157
163
|
supportsToolChoice: false,
|
|
158
164
|
inputModalities: modalities,
|
|
159
|
-
price
|
|
165
|
+
price,
|
|
160
166
|
priceTiers: [],
|
|
161
167
|
quality: twin === null ? {} : { ...twin.quality },
|
|
162
168
|
tokenizer: twin?.tokenizer ?? "Other",
|
|
@@ -167,7 +173,10 @@ export function buildOllamaModels(args: BuildOllamaArgs): CatalogModel[] {
|
|
|
167
173
|
if (twin?.maxCompletionTokens !== undefined) model.maxCompletionTokens = twin.maxCompletionTokens;
|
|
168
174
|
out.push(model);
|
|
169
175
|
}
|
|
170
|
-
|
|
176
|
+
// A model the provider offers and the router refuses to route is operationally
|
|
177
|
+
// significant, not a debug detail: at `debug` this was invisible, and a newly published
|
|
178
|
+
// model stayed unroutable with nothing in the log to say so.
|
|
179
|
+
if (skipped.length > 0) log?.warn("ollama models listed but not routable", { skipped: skipped.join(", ") });
|
|
171
180
|
return out;
|
|
172
181
|
}
|
|
173
182
|
|
package/src/server/http.ts
CHANGED
|
@@ -723,14 +723,34 @@ export function startServer(cfg: RouterConfig): StartedServer {
|
|
|
723
723
|
const named = Array.isArray(body?.anchors) ? (body.anchors as unknown[]).filter((a): a is string => typeof a === "string") : [];
|
|
724
724
|
const anchors = named.length > 0 ? [...new Set(named)] : pickAnchors(models, slug);
|
|
725
725
|
if (anchors.length < MIN_ANCHORS) return wireErrorResponse({ status: 422, code: "invalid_request_error", message: `need at least ${MIN_ANCHORS} scored, tool-capable anchor models to calibrate against` });
|
|
726
|
+
// A rate-limited dispatch is not a failed task. Ollama Cloud answers "too many
|
|
727
|
+
// concurrent requests" well below four models in flight, and the runner folds a
|
|
728
|
+
// throwing completion in as grade 0 — which would score a provider's throttle as
|
|
729
|
+
// the model being wrong. Retry with backoff, and keep the default concurrency
|
|
730
|
+
// low enough that the throttle is rarely reached in the first place.
|
|
726
731
|
const complete: Completer = async (target, messages) => {
|
|
727
|
-
|
|
728
|
-
|
|
732
|
+
let last: unknown = null;
|
|
733
|
+
for (let attempt = 0; attempt < 4; attempt++) {
|
|
734
|
+
if (attempt > 0) {
|
|
735
|
+
const { promise, resolve } = Promise.withResolvers<void>();
|
|
736
|
+
setTimeout(resolve, attempt * 4000);
|
|
737
|
+
await promise;
|
|
738
|
+
}
|
|
739
|
+
try {
|
|
740
|
+
const out = await upstream.complete({ model: target, stream: false, temperature: 0, max_tokens: 1024, messages }, AbortSignal.timeout(120_000));
|
|
741
|
+
return out.text;
|
|
742
|
+
} catch (err) {
|
|
743
|
+
last = err;
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
throw last instanceof Error ? last : new Error(String(last));
|
|
729
747
|
};
|
|
730
748
|
const judgeSlug = typeof body?.judge === "string" && body.judge !== "" ? body.judge : "";
|
|
749
|
+
const asked = typeof body?.concurrency === "number" ? Math.floor(body.concurrency) : 2;
|
|
731
750
|
const results = await runEval({
|
|
732
751
|
slugs: [slug, ...anchors],
|
|
733
752
|
complete,
|
|
753
|
+
concurrency: Math.min(Math.max(1, asked), 8),
|
|
734
754
|
...(judgeSlug === "" ? {} : { judge: makeJudge(complete, judgeSlug) }),
|
|
735
755
|
});
|
|
736
756
|
const target = results[0]!;
|
package/test/ollama.test.ts
CHANGED
|
@@ -164,6 +164,22 @@ describe("buildOllamaModels", () => {
|
|
|
164
164
|
expect(ds.quality).toEqual({});
|
|
165
165
|
});
|
|
166
166
|
|
|
167
|
+
test("a model Ollama lists but the price table does not know is priced from its twin", () => {
|
|
168
|
+
// Ollama publishes no prices, so they come from a static table. deepseek-v4.1-flash was
|
|
169
|
+
// listed by /api/tags and dropped here for want of an entry, so routing never saw it at
|
|
170
|
+
// all — a model the provider was actively offering. The twin sells the same weights.
|
|
171
|
+
const unpriced = { id: "glm-5.3-flash:cloud", remoteModel: "not-in-the-price-table", isCloud: true, contextLength: 262_144, capabilities: ["tools"], modifiedAtMs: 0 };
|
|
172
|
+
const twinPriced = buildOllamaModels({ listings: [{ ...unpriced, remoteModel: "glm-5.3-flash" }], openrouter: OR_MODELS, cfg: OLLAMA, log });
|
|
173
|
+
const or = OR_MODELS.find((m) => m.slug === "z-ai/glm-5.3-flash")!;
|
|
174
|
+
expect(twinPriced).toHaveLength(1);
|
|
175
|
+
expect(twinPriced[0]!.price.prompt).toBeCloseTo(0.15 / 1e6, 12);
|
|
176
|
+
|
|
177
|
+
// Unknown to the table AND matching no twin: still skipped, never priced at zero.
|
|
178
|
+
const orphan = buildOllamaModels({ listings: [{ ...unpriced, id: "brand-new-thing:cloud", remoteModel: "brand-new-thing" }], openrouter: OR_MODELS, cfg: OLLAMA, log });
|
|
179
|
+
expect(orphan).toEqual([]);
|
|
180
|
+
expect(or.price.prompt).toBeGreaterThan(0);
|
|
181
|
+
});
|
|
182
|
+
|
|
167
183
|
test("a pinned twin beats the name match", () => {
|
|
168
184
|
const cfg: OllamaConfig = { ...OLLAMA, twins: { "deepseek-v4-pro": "moonshotai/kimi-k3" } };
|
|
169
185
|
const ds = buildOllamaModels({ listings: listings(), openrouter: OR_MODELS, cfg, log }).find((m) => m.slug.startsWith("ollama/deepseek"))!;
|