fix(catalog): cap ollama cloud deepseek-v4 output at 65536

Ollama Cloud's deepseek-v4-pro and deepseek-v4-flash deployments reject any
output budget above 65536 with HTTP 400, despite advertising a 1M context /
384K output (ollama/ollama#16890). Ollama's /api/show never reports this cap,
so the catalog left the base models at the full context window and the dated
tag deepseek-v4-flash:0731 at a stale 8192 fallback. Pin these ids (base plus
tag variants) to min(contextWindow, 65536) at both runtime discovery and
generation; other cloud models keep their discovered limits.

Fixes #7266
This commit is contained in:
roboomp
2026-08-01 13:09:47 +00:00
parent 8baa3300bc
commit c00790fa3a
7 changed files with 221 additions and 13 deletions
+4
View File
@@ -2,6 +2,10 @@
## [Unreleased]
### Fixed
- Fixed Ollama Cloud DeepSeek V4 Pro/Flash models (including dated tag variants such as `deepseek-v4-flash:0731`) reporting an incorrect max-output-tokens figure by pinning it to the deployment's enforced 65536-token output ceiling ([#7266](https://github.com/can1357/oh-my-pi/issues/7266)).
## [17.2.3] - 2026-08-01
### Added
@@ -55,6 +55,7 @@ import { JWT_CLAIM_PATH } from "../src/wire/codex";
import {
applyCanonicalLimitFallback,
applyGeneratedModelPolicies,
applyOllamaCloudOutputCap,
CLOUDFLARE_FALLBACK_MODEL,
dropUnsupportedBedrockGeoIds,
linkOpenAIPromotionTargets,
@@ -678,6 +679,9 @@ async function generateModels() {
// Fill remaining null endpoint limits from each model's canonical-family
// reference. Runs last so canonical ids and explicit policy limits are final.
applyCanonicalLimitFallback(allModels);
// Pin every Ollama Cloud model's max-output to the enforced ceiling; runs
// after canonical fallback so finalized context windows drive the cap.
applyOllamaCloudOutputCap(allModels);
for (const model of allModels) {
canonicalizeModelCompat(model);
@@ -18,6 +18,7 @@ import { isMimoModelIdOrName } from "../src/identity/family";
import { getLongestModelLikeIdSegment } from "../src/identity/id";
import { buildModelReferenceIndex, resolveModelReference } from "../src/identity/reference";
import { resolveModelThinking } from "../src/model-thinking";
import { isOllamaCloudOutputCapped, OLLAMA_CLOUD_MAX_OUTPUT_TOKENS } from "../src/provider-models/ollama";
import {
ALIBABA_TOKEN_PLAN_STATIC_MODELS,
resolveWaferServerlessThinkingFormat,
@@ -220,6 +221,26 @@ export function applyCanonicalLimitFallback(models: ModelSpec<Api>[]): void {
}
}
/**
* Pin the max-output figure for Ollama Cloud models whose deployment enforces a
* lower ceiling than their advertised window.
*
* Ollama's `/api/show` never reports a per-model output cap, so discovery and
* previous snapshots leave `maxTokens` at the full context window (or a stale
* conservative fallback, as with `deepseek-v4-flash:0731`). DeepSeek V4
* Pro/Flash deployments actually reject any output budget above
* {@link OLLAMA_CLOUD_MAX_OUTPUT_TOKENS} (ollama/ollama#16890, #3392/#3394), so
* pin those ids to `min(contextWindow, ceiling)` — the true amount the endpoint
* accepts (#7266). Other cloud models keep their discovered limits.
*/
export function applyOllamaCloudOutputCap(models: ModelSpec<Api>[]): void {
for (const model of models) {
if (model.provider !== "ollama-cloud" || model.contextWindow === null) continue;
if (!isOllamaCloudOutputCapped(model.id)) continue;
model.maxTokens = Math.min(model.contextWindow, OLLAMA_CLOUD_MAX_OUTPUT_TOKENS);
}
}
function applyGeneratedModelPolicy(model: ModelSpec<Api>): void {
const copilotLimits = model.provider === "github-copilot" ? COPILOT_GENERATED_LIMITS[model.id] : undefined;
if (copilotLimits) {
+3 -3
View File
@@ -68099,7 +68099,7 @@
"cacheWrite": 0
},
"contextWindow": 1048576,
"maxTokens": 1048576,
"maxTokens": 65536,
"omitMaxOutputTokens": true,
"thinking": {
"mode": "effort",
@@ -68139,7 +68139,7 @@
"cacheWrite": 0
},
"contextWindow": 1048576,
"maxTokens": 8192,
"maxTokens": 65536,
"omitMaxOutputTokens": true,
"supportsComputerUse": false
},
@@ -68160,7 +68160,7 @@
"cacheWrite": 0
},
"contextWindow": 1048576,
"maxTokens": 1048576,
"maxTokens": 65536,
"omitMaxOutputTokens": true,
"thinking": {
"mode": "effort",
+37 -6
View File
@@ -23,6 +23,36 @@ type OllamaShowResponse = {
};
const OLLAMA_RETRY_DELAYS_MS = [2_000, 5_000, 10_000];
/**
* Output-token ceiling that Ollama Cloud enforces for the DeepSeek V4 Pro/Flash
* deployments: `/api/chat` rejects `num_predict` above it with HTTP 400
* (`max_tokens (...) exceeds model's maximum output tokens (65536)`) even though
* the model pages advertise a 1M context / 384K output. Ollama's `/api/show`
* never reports this cap, so the catalog pins it for the affected models
* (ollama/ollama#16890, #7266). The wire layer clamps `num_predict` to the same
* value (`OLLAMA_CLOUD_NUM_PREDICT_CAP` in `packages/ai/src/providers/ollama.ts`,
* #3392/#3394).
*/
export const OLLAMA_CLOUD_MAX_OUTPUT_TOKENS = 65_536;
/**
* Untagged base ids whose Ollama Cloud deployment enforces
* {@link OLLAMA_CLOUD_MAX_OUTPUT_TOKENS}. Only DeepSeek V4 Pro/Flash are known
* to cap output below their advertised window (ollama/ollama#16890); other cloud
* models keep their discovered limits.
*/
const OLLAMA_CLOUD_OUTPUT_CAPPED_BASE_IDS: Record<string, true> = {
"deepseek-v4-flash": true,
"deepseek-v4-pro": true,
};
/** Whether an Ollama Cloud model id (tagged or not) enforces the 65536 output cap. */
export function isOllamaCloudOutputCapped(id: string): boolean {
const separator = id.indexOf(":");
const baseId = separator > 0 ? id.slice(0, separator) : id;
return OLLAMA_CLOUD_OUTPUT_CAPPED_BASE_IDS[baseId] === true;
}
const OLLAMA_CLOUD_GLM_52_THINKING: ThinkingConfig = {
mode: "effort",
efforts: [Effort.High, Effort.Max],
@@ -133,10 +163,10 @@ export function ollamaCloudModelManagerOptions(
}
const capabilities = metadata?.capabilities;
const discoveredContextWindow = getContextWindow(metadata?.model_info);
// `/api/show` is the only trustworthy Ollama-owned source for size caps.
// When it is unavailable (or returns only coarse capabilities), do NOT
// inherit giant budgets from bundled fallback metadata sourced from a
// different catalog; keep the historical safe fallback instead.
// `/api/show` reports the context length but never a per-model output
// cap. DeepSeek V4 Pro/Flash deployments enforce a 65536 output ceiling
// (ollama/ollama#16890, #7266); every other id keeps the trusted
// reference limit, falling back to the historical safe cap otherwise.
const contextWindow = discoveredContextWindow ?? 128000;
const reasoning = capabilities ? capabilities.includes("thinking") : (reference?.reasoning ?? false);
const thinking = capabilities ? getThinkingConfig(id, capabilities) : reference?.thinking;
@@ -157,8 +187,9 @@ export function ollamaCloudModelManagerOptions(
input,
cost: reference?.cost ?? { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
contextWindow,
maxTokens:
discoveredContextWindow !== null && discoveredContextWindow !== undefined
maxTokens: isOllamaCloudOutputCapped(id)
? Math.min(contextWindow, OLLAMA_CLOUD_MAX_OUTPUT_TOKENS)
: discoveredContextWindow !== null && discoveredContextWindow !== undefined
? (providerReference?.maxTokens ?? Math.min(contextWindow, 8192))
: Math.min(contextWindow, 8192),
omitMaxOutputTokens: true,
@@ -1,7 +1,11 @@
import { describe, expect, it } from "bun:test";
import { Effort } from "@oh-my-pi/pi-catalog/effort";
import type { Api, ModelSpec, Provider } from "@oh-my-pi/pi-catalog/types";
import { applyGeneratedModelPolicies, linkOpenAIPromotionTargets } from "../scripts/generated-policies";
import {
applyGeneratedModelPolicies,
applyOllamaCloudOutputCap,
linkOpenAIPromotionTargets,
} from "../scripts/generated-policies";
function createSpec<TApi extends Api>(overrides: {
id: string;
@@ -408,3 +412,93 @@ describe("generated model policies", () => {
expect(models[3]?.applyPatchToolType).toBeUndefined();
});
});
describe("applyOllamaCloudOutputCap", () => {
it("pins DeepSeek V4 Pro/Flash (and their tag variants) to the enforced ceiling (#7266)", () => {
const models: ModelSpec<Api>[] = [
createSpec({
id: "deepseek-v4-flash",
api: "ollama-chat",
provider: "ollama-cloud",
contextWindow: 1048576,
maxTokens: 1048576,
}),
createSpec({
id: "deepseek-v4-flash:0731",
api: "ollama-chat",
provider: "ollama-cloud",
contextWindow: 1048576,
maxTokens: 8192,
}),
createSpec({
id: "deepseek-v4-pro",
api: "ollama-chat",
provider: "ollama-cloud",
contextWindow: 1048576,
maxTokens: 1048576,
}),
];
applyOllamaCloudOutputCap(models);
expect(models[0]?.maxTokens).toBe(65536);
expect(models[1]?.maxTokens).toBe(65536);
expect(models[2]?.maxTokens).toBe(65536);
});
it("leaves other Ollama Cloud models' discovered limits untouched", () => {
const models: ModelSpec<Api>[] = [
createSpec({
id: "kimi-k2.5",
api: "ollama-chat",
provider: "ollama-cloud",
contextWindow: 262144,
maxTokens: 262144,
}),
createSpec({
id: "deepseek-v3.1:671b",
api: "ollama-chat",
provider: "ollama-cloud",
contextWindow: 163840,
maxTokens: 163840,
}),
];
applyOllamaCloudOutputCap(models);
expect(models[0]?.maxTokens).toBe(262144);
expect(models[1]?.maxTokens).toBe(163840);
});
it("caps by the context window when a capped model's window is below the ceiling", () => {
const models: ModelSpec<Api>[] = [
createSpec({
id: "deepseek-v4-flash",
api: "ollama-chat",
provider: "ollama-cloud",
contextWindow: 32768,
maxTokens: 32768,
}),
];
applyOllamaCloudOutputCap(models);
expect(models[0]?.maxTokens).toBe(32768);
});
it("does not touch other providers", () => {
const models: ModelSpec<Api>[] = [
createSpec({
id: "deepseek-v4-flash",
api: "openai-completions",
provider: "deepseek",
contextWindow: 1048576,
maxTokens: 1048576,
}),
];
applyOllamaCloudOutputCap(models);
expect(models[0]?.maxTokens).toBe(1048576);
});
});
@@ -26,7 +26,7 @@ test("ollama-cloud discovery does not inherit unsafe cross-provider maxTokens",
const fetchMock: FetchImpl = vi.fn(async (input, _init) => {
const url = String(input);
if (url === "https://ollama.com/api/tags") {
return new Response(JSON.stringify({ models: [{ name: "deepseek-v4-flash" }] }), {
return new Response(JSON.stringify({ models: [{ name: "kimi-k2.5" }] }), {
status: 200,
headers: { "Content-Type": "application/json" },
});
@@ -42,12 +42,66 @@ test("ollama-cloud discovery does not inherit unsafe cross-provider maxTokens",
const options = ollamaCloudModelManagerOptions({ apiKey: "cloud-test-key", fetch: fetchMock });
const models = await options.fetchDynamicModels?.();
const model = models?.find(candidate => candidate.id === "deepseek-v4-flash");
const model = models?.find(candidate => candidate.id === "kimi-k2.5");
expect(model?.contextWindow).toBe(128000);
expect(model?.maxTokens).toBe(8192);
});
test("ollama-cloud discovery caps discovered max-output at the enforced ceiling (#7266)", async () => {
const fetchMock: FetchImpl = vi.fn(async (input, _init) => {
const url = String(input);
if (url === "https://ollama.com/api/tags") {
return new Response(JSON.stringify({ models: [{ name: "deepseek-v4-flash:0731" }] }), {
status: 200,
headers: { "Content-Type": "application/json" },
});
}
if (url === "https://ollama.com/api/show") {
return new Response(
JSON.stringify({ capabilities: ["completion"], model_info: { "deepseek.context_length": 1048576 } }),
{ status: 200, headers: { "Content-Type": "application/json" } },
);
}
throw new Error(`Unexpected URL: ${url}`);
});
const options = ollamaCloudModelManagerOptions({ apiKey: "cloud-test-key", fetch: fetchMock });
const models = await options.fetchDynamicModels?.();
const model = models?.find(candidate => candidate.id === "deepseek-v4-flash:0731");
expect(model?.contextWindow).toBe(1048576);
// Ollama Cloud rejects output budgets above 65536, so the 1M context window
// must not surface as the max-output figure.
expect(model?.maxTokens).toBe(65536);
});
test("ollama-cloud discovery caps a capped model by its context window when below the ceiling", async () => {
const fetchMock: FetchImpl = vi.fn(async (input, _init) => {
const url = String(input);
if (url === "https://ollama.com/api/tags") {
return new Response(JSON.stringify({ models: [{ name: "deepseek-v4-flash:mini" }] }), {
status: 200,
headers: { "Content-Type": "application/json" },
});
}
if (url === "https://ollama.com/api/show") {
return new Response(
JSON.stringify({ capabilities: ["completion"], model_info: { "deepseek.context_length": 32768 } }),
{ status: 200, headers: { "Content-Type": "application/json" } },
);
}
throw new Error(`Unexpected URL: ${url}`);
});
const options = ollamaCloudModelManagerOptions({ apiKey: "cloud-test-key", fetch: fetchMock });
const models = await options.fetchDynamicModels?.();
const model = models?.find(candidate => candidate.id === "deepseek-v4-flash:mini");
expect(model?.contextWindow).toBe(32768);
expect(model?.maxTokens).toBe(32768);
});
test("ollama-cloud discovery always omits max output tokens", async () => {
const fetchMock: FetchImpl = vi.fn(async (input, _init) => {
const url = String(input);
@@ -78,7 +132,7 @@ test("ollama-cloud discovery always omits max output tokens", async () => {
expect(model?.provider).toBe("ollama-cloud");
expect(model?.contextWindow).toBe(1048576);
expect(model?.maxTokens).toBe(1048576);
expect(model?.maxTokens).toBe(65536);
expect(model?.omitMaxOutputTokens).toBe(true);
});