fix(models): expose Ollama Cloud native effort tiers (#11307)

Validated on a 17-PR combined board: models-catalog-combo-metadata + ollama-cloud-reasoning-effort-tiers-10788 within the board's 287/287, typecheck:core clean, check:open-sse-typecheck clean, vitest 405/405. Publishes Ollama Cloud's native none/low/medium/high/max effort vocabulary for reasoning-capable passthrough/tagged models with no exact registry declaration, adds none to DeepSeek V4/GLM 5.x, and preserves narrower exact-model vocabularies (GPT-OSS) via intersection. Refs #10788. Thank you @ekinnee!
This commit is contained in:
Erick Kinnee
2026-08-23 23:55:47 -05:00
committed by GitHub
parent 6945bbaaba
commit 338c05dc6a
8 changed files with 164 additions and 20 deletions

View File

@@ -181,6 +181,29 @@ export function getRegistryEntry(provider: string): RegistryEntry | null {
return REGISTRY[provider] || _byAlias.get(provider) || null;
}
/** Resolve only a model's explicit reasoning vocabulary. */
export function getRegistryModelThinkingEfforts(
provider: string,
modelId: string
): readonly string[] | undefined {
const entry = getRegistryEntry(provider);
if (!entry) return undefined;
const model = entry.models.find((candidate) => candidate.id === modelId);
return model?.supportedThinkingEfforts;
}
/** Resolve a model's explicit reasoning vocabulary before its provider fallback. */
export function getRegistryThinkingEfforts(
provider: string,
modelId: string
): readonly string[] | undefined {
const entry = getRegistryEntry(provider);
if (!entry) return undefined;
const modelEfforts = getRegistryModelThinkingEfforts(provider, modelId);
if (modelEfforts !== undefined) return modelEfforts;
return entry.defaultSupportedThinkingEfforts;
}
/**
* Decide whether a non-empty live catalog may exclude omitted static models
* during request routing and wildcard expansion.

View File

@@ -9,6 +9,7 @@ export const ollama_cloudProvider: RegistryEntry = {
modelsUrl: "https://ollama.com/api/tags",
authType: "apikey",
authHeader: "bearer",
defaultSupportedThinkingEfforts: ["none", "low", "medium", "high", "max"],
// Note: rate limits vary by plan (free = "Light usage", Pro = more, Max = 5x Pro).
// Users can generate API keys at https://ollama.com/settings/keys
models: [
@@ -24,23 +25,20 @@ export const ollama_cloudProvider: RegistryEntry = {
supportsReasoning: true,
supportedThinkingEfforts: ["low", "medium", "high"],
},
// #10788: Ollama Cloud accepts low|medium|high|max|none uniformly across
// its reasoning-capable models (see supportsMaxEffortForProvider's
// isOllamaCloud comment in open-sse/executors/base/reasoningEffort.ts) —
// declare supportedThinkingEfforts so appendSyncedEffortVariants() (which
// runs before static-model capability enrichment) can synthesize the
// catalog's selectable -low/-high/-max variant ids for these models.
// #10788: these models accept none|low|medium|high|max. Keep their explicit
// declarations aligned with the provider fallback so the static and synced
// catalog paths expose the same native vocabulary.
{
id: "deepseek-v4-pro",
name: "DeepSeek V4 Pro",
supportsReasoning: true,
supportedThinkingEfforts: ["low", "medium", "high", "max"],
supportedThinkingEfforts: ["none", "low", "medium", "high", "max"],
},
{
id: "deepseek-v4-flash",
name: "DeepSeek V4 Flash",
supportsReasoning: true,
supportedThinkingEfforts: ["low", "medium", "high", "max"],
supportedThinkingEfforts: ["none", "low", "medium", "high", "max"],
},
{ id: "kimi-k2.6", name: "Kimi K2.6" },
// Ollama Cloud accepts low|medium|high|max|none and rejects xhigh, so the
@@ -50,14 +48,14 @@ export const ollama_cloudProvider: RegistryEntry = {
name: "GLM 5.1",
supportsReasoning: true,
supportsXHighEffort: false,
supportedThinkingEfforts: ["low", "medium", "high", "max"],
supportedThinkingEfforts: ["none", "low", "medium", "high", "max"],
},
{
id: "glm-5.2",
name: "GLM 5.2",
supportsReasoning: true,
supportsXHighEffort: false,
supportedThinkingEfforts: ["low", "medium", "high", "max"],
supportedThinkingEfforts: ["none", "low", "medium", "high", "max"],
},
// #3110: MiniMax M3 via Ollama
{ id: "minimax-m3", name: "MiniMax M3", contextLength: 1048576, supportsVision: true },

View File

@@ -139,6 +139,9 @@ export interface RegistryEntry {
requestDefaults?: ProviderRequestDefaults;
oauth?: RegistryOAuth;
models: RegistryModel[];
/** Provider-native reasoning vocabulary for reasoning-capable passthrough models
* that do not have an explicit per-model declaration. */
defaultSupportedThinkingEfforts?: readonly string[];
modelsUrl?: string;
/** Prefix to prepend to model IDs before upstream API calls (e.g. "accounts/fireworks/models/") */
modelIdPrefix?: string;

View File

@@ -28,7 +28,11 @@ import { getAllAudioModels } from "@omniroute/open-sse/config/audioRegistry";
import { getAllModerationModels } from "@omniroute/open-sse/config/moderationRegistry";
import { getAllVideoModels } from "@omniroute/open-sse/config/videoRegistry";
import { getAllMusicModels } from "@omniroute/open-sse/config/musicRegistry";
import { REGISTRY } from "@omniroute/open-sse/config/providerRegistry";
import {
getRegistryModelThinkingEfforts,
getRegistryThinkingEfforts,
REGISTRY,
} from "@omniroute/open-sse/config/providerRegistry";
import { CODEX_NATIVE_UNPREFIXED_MODELS } from "@omniroute/open-sse/services/model";
import { isModelSelectable } from "@omniroute/open-sse/services/modelLifecycle";
import { resolveNestedComboTargets } from "@omniroute/open-sse/services/combo";
@@ -585,7 +589,9 @@ async function buildUnifiedModelsResponseCore(
modelId,
target,
eligibleConnectionIds,
connectionCatalog || {}
connectionCatalog || {},
getRegistryModelThinkingEfforts(providerId, modelId),
getRegistryThinkingEfforts(providerId, modelId)
);
if (
connectionEfforts === undefined &&
@@ -669,7 +675,7 @@ async function buildUnifiedModelsResponseCore(
providerId,
modelId,
canonical.capabilities.supportsThinking,
registryModel?.supportedThinkingEfforts,
getRegistryThinkingEfforts(providerId, modelId),
true
)
: getThinkingCapabilityFields(

View File

@@ -36,6 +36,7 @@ export type ComboCatalogTarget = {
type ConnectionScopedReasoningModel = {
id: string;
supportsThinking?: boolean;
supportedThinkingEfforts?: string[];
};
@@ -106,7 +107,9 @@ export function getConnectionScopedEffortTiers(
modelId: string,
target: Pick<ComboCatalogTarget, "connectionId" | "allowedConnectionIds">,
eligibleConnectionIds: readonly string[] | undefined,
modelsByConnection: ConnectionScopedReasoningCatalog
modelsByConnection: ConnectionScopedReasoningCatalog,
explicitThinkingEfforts?: readonly string[],
fallbackThinkingEfforts?: readonly string[]
): string[] | undefined {
const eligible = eligibleConnectionIds ? new Set(eligibleConnectionIds) : undefined;
if (target.connectionId && eligible && !eligible.has(target.connectionId)) return [];
@@ -139,7 +142,16 @@ export function getConnectionScopedEffortTiers(
);
if (matching.some((model) => model === undefined)) return [];
const efforts = matching.map((model) => model?.supportedThinkingEfforts || []);
const efforts = matching.map((model) => {
const resolved = model?.supportedThinkingEfforts?.length
? model.supportedThinkingEfforts
: model?.supportsThinking === true && fallbackThinkingEfforts
? [...fallbackThinkingEfforts]
: [];
return explicitThinkingEfforts
? explicitThinkingEfforts.filter((effort) => resolved.includes(effort))
: resolved;
});
return intersectStringArrays(efforts);
}

View File

@@ -27,9 +27,14 @@
// refactors. (Confirmed convention: grep "from \"@omniroute/open-sse" src/app/api/v1/models/)
import { getLearnedReasoningEffortForModel } from "@omniroute/open-sse/services/learnedReasoningEffortCaps.ts";
import { isSkippedEffortProvider } from "@omniroute/open-sse/utils/syncedEffortVariants.ts";
import {
getRegistryModelThinkingEfforts,
getRegistryThinkingEfforts,
} from "@omniroute/open-sse/config/providerRegistry.ts";
interface SyncedCapabilityFlags {
id?: string;
supportsThinking?: boolean;
supportsVision?: boolean;
supportedThinkingEfforts?: string[];
}
@@ -37,10 +42,23 @@ interface SyncedCapabilityFlags {
function effectiveEffortTiers(sm: SyncedCapabilityFlags, ownedBy: string): string[] | undefined {
if (isSkippedEffortProvider(ownedBy)) return undefined;
const learned = sm.id ? getLearnedReasoningEffortForModel(sm.id) : null;
const synced =
Array.isArray(sm.supportedThinkingEfforts) && sm.supportedThinkingEfforts.length > 0
? sm.supportedThinkingEfforts
: null;
const explicit = sm.id ? getRegistryModelThinkingEfforts(ownedBy, sm.id) : undefined;
if (explicit) {
const observed = learned ? [...learned] : synced;
const narrowed = observed
? explicit.filter((effort) => observed.includes(effort))
: [...explicit];
return narrowed.length > 0 ? narrowed : undefined;
}
if (learned) return [...learned];
return Array.isArray(sm.supportedThinkingEfforts) && sm.supportedThinkingEfforts.length > 0
? sm.supportedThinkingEfforts
: undefined;
if (synced) return synced;
if (!sm.supportsThinking || !sm.id) return undefined;
const registryEfforts = getRegistryThinkingEfforts(ownedBy, sm.id);
return registryEfforts && registryEfforts.length > 0 ? [...registryEfforts] : undefined;
}
/** Build the `capabilities` object for a fresh synced-model catalog entry, or `undefined` when neither flag applies. */

View File

@@ -567,3 +567,64 @@ test("mixed DeepSeek combos advertise the efforts accepted by every V4 target",
]);
}
});
test("Ollama Cloud projects native efforts for base, tagged, and combo models", async () => {
const provider = "ollama-cloud";
const baseModel = "deepseek-v4-flash";
const taggedModel = "deepseek-v4-flash:0731";
const narrowModel = "gpt-oss:20b";
const nativeEfforts = ["none", "low", "medium", "high", "max"];
const narrowEfforts = ["low", "medium", "high"];
const connection = await providersDb.createProviderConnection({
provider,
authType: "apikey",
name: "ollama-cloud-native-efforts",
apiKey: "ollama-cloud-test-key",
isActive: true,
testStatus: "active",
});
await modelsDb.replaceSyncedAvailableModelsForConnection(provider, connection.id, [
{ id: baseModel, name: "DeepSeek V4 Flash", supportsThinking: true },
{ id: taggedModel, name: "DeepSeek V4 Flash 0731", supportsThinking: true },
{
id: narrowModel,
name: "GPT-OSS 20B",
supportsThinking: true,
supportedThinkingEfforts: nativeEfforts,
},
]);
await combosDb.createCombo({
name: "ollama-cloud-native-efforts-combo",
strategy: "auto",
models: [`${provider}/${baseModel}`, `${provider}/${taggedModel}`],
});
await combosDb.createCombo({
name: "ollama-cloud-narrow-efforts-combo",
strategy: "auto",
models: [`${provider}/${narrowModel}`],
});
const response = await catalog.getUnifiedModelsResponse(
new Request("http://localhost/api/v1/models")
);
const body = (await response.json()) as { data: Array<Record<string, unknown>> };
const capabilitiesFor = (modelId: string) => {
const model = body.data.find((item) => item.id === modelId);
assert.ok(model, modelId);
return model.capabilities as Record<string, unknown>;
};
assert.equal(response.status, 200);
for (const modelId of [
`ollamacloud/${baseModel}`,
`ollamacloud/${taggedModel}`,
"ollama-cloud-native-efforts-combo",
]) {
const effortTiers = capabilitiesFor(modelId).effort_tiers;
assert.deepEqual(effortTiers, nativeEfforts, modelId);
assert.equal((effortTiers as string[]).includes("xhigh"), false, modelId);
}
for (const modelId of [`ollamacloud/${narrowModel}`, "ollama-cloud-narrow-efforts-combo"]) {
assert.deepEqual(capabilitiesFor(modelId).effort_tiers, narrowEfforts, modelId);
}
});

View File

@@ -1,6 +1,7 @@
import { test } from "node:test";
import assert from "node:assert/strict";
import { ollama_cloudProvider } from "../../open-sse/config/providers/registry/ollama-cloud/index.ts";
import { getRegistryThinkingEfforts } from "../../open-sse/config/providerRegistry.ts";
// #10788: ollama-cloud declared supportsReasoning:true on several models
// (glm-5.1/5.2, deepseek-v4-pro/flash) but never declared
@@ -19,6 +20,7 @@ test("#10788: ollama-cloud reasoning-capable models declare supportedThinkingEff
Array.isArray(control?.supportedThinkingEfforts) && control.supportedThinkingEfforts.length > 0,
"control: gpt-oss:20b should already declare supportedThinkingEfforts"
);
assert.deepEqual(control.supportedThinkingEfforts, ["low", "medium", "high"]);
const reasoningModelIds = ["glm-5.1", "glm-5.2", "deepseek-v4-pro", "deepseek-v4-flash"];
for (const id of reasoningModelIds) {
@@ -33,8 +35,29 @@ test("#10788: ollama-cloud reasoning-capable models declare supportedThinkingEff
// low|medium|high|max|none — xhigh is rejected and mapped to max.
assert.deepEqual(
[...(model?.supportedThinkingEfforts ?? [])],
["low", "medium", "high", "max"],
`${id} should declare Ollama Cloud's documented low/medium/high/max vocabulary`
["none", "low", "medium", "high", "max"],
`${id} should declare Ollama Cloud's documented none/low/medium/high/max vocabulary`
);
}
});
test("#10788: provider fallback preserves explicit and unrelated vocabularies", () => {
assert.deepEqual(getRegistryThinkingEfforts("ollama-cloud", "deepseek-v4-flash:0731"), [
"none",
"low",
"medium",
"high",
"max",
]);
assert.deepEqual(getRegistryThinkingEfforts("ollama-cloud", "gpt-oss:20b"), [
"low",
"medium",
"high",
]);
assert.deepEqual(getRegistryThinkingEfforts("deepseek", "deepseek-v4-flash"), [
"none",
"low",
"high",
"max",
]);
});