mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-15 19:52:50 +03:00
* fix(api): reach DeepSeek V4's native max reasoning tier DeepSeek V4 accepts reasoning_effort low | high | max, defaults to high, and maps medium and xhigh down to high (https://api-docs.deepseek.com/api/create-chat-completion; the upstream 400 on an invalid value enumerates none, minimal, low, medium, high, xhigh, max). OmniRoute's canonical vocabulary is none|low|medium|high|xhigh, where `max` is an alias collapsing onto `xhigh`. DeepSeek then maps `xhigh` back down to `high`, so a client sending {"effort":"max"} silently got high — the model's top reasoning tier was unreachable through the canonical field, and the catalog never advertised `max` as an available tier. Mirror the existing extendCodexGpt56EffortValues precedent: expose the provider-native tier for these models only, without widening the global request vocabulary. CANONICAL_EFFORT_VALUES and normalizeEffort() are unchanged, so every other provider keeps collapsing max -> xhigh. Scoped to the native `deepseek`/`ds` provider. Routed namespaces that merely carry "deepseek" in the id (openrouter/deepseek/..., tllm/deepseek_v4, oc/deepseek-v4-flash-free) terminate at a different upstream whose effort vocabulary we do not control, so they keep the canonical behavior. The provider is not resolved yet where the canonical params are folded in (chat.ts), so the check also accepts a `<prefix>/<model>` id. An explicit client reasoning_effort / reasoning.effort still wins, as before. Covered by tests/unit/deepseek-native-max-effort.test.ts: 4 of its 6 cases fail without this change. * chore(changelog): add fragment for #10230
105 lines
4.5 KiB
TypeScript
105 lines
4.5 KiB
TypeScript
/**
|
|
* DeepSeek V4 exposes a native `max` reasoning tier that the canonical vocabulary erases.
|
|
*
|
|
* Per https://api-docs.deepseek.com/api/create-chat-completion the accepted
|
|
* `reasoning_effort` values are `low`, `high` and `max`, the default is `high`, and
|
|
* **`medium` / `xhigh` are both mapped to `high`** upstream. (The live API's 400 on an
|
|
* invalid value enumerates the full accepted set:
|
|
* `none`, `minimal`, `low`, `medium`, `high`, `xhigh`, `max`.)
|
|
*
|
|
* OmniRoute's canonical vocabulary is `none|low|medium|high|xhigh`, and `max` is an alias
|
|
* that collapses onto `xhigh` (EFFORT_TIER_ALIASES). Since DeepSeek then maps `xhigh` back
|
|
* down to `high`, a client sending `{"effort":"max"}` silently received **high** — the top
|
|
* tier was unreachable through the canonical field.
|
|
*
|
|
* The fix mirrors the existing `extendCodexGpt56EffortValues` precedent: expose the
|
|
* provider-native tier for these models only, without widening the global request
|
|
* vocabulary for every other provider.
|
|
*
|
|
* Guards: A = `max` survives for native DeepSeek models; B = every other provider still
|
|
* collapses `max`→`xhigh`; C = routed DeepSeek namespaces (openrouter/tllm) are NOT treated
|
|
* as native; D = an explicit client `reasoning_effort` still wins; E = the catalog offers
|
|
* `max` as an effort tier for native DeepSeek models.
|
|
*/
|
|
import test from "node:test";
|
|
import assert from "node:assert/strict";
|
|
|
|
const {
|
|
normalizeReasoningRequest,
|
|
normalizeEffort,
|
|
isDeepSeekNativeMaxModel,
|
|
extendDeepSeekEffortValues,
|
|
CANONICAL_EFFORT_VALUES,
|
|
} = await import("../../src/shared/reasoning/effortStandardization.ts");
|
|
|
|
test("A: canonical effort `max` survives for native DeepSeek V4 models", () => {
|
|
for (const model of [
|
|
"ds/deepseek-v4-pro",
|
|
"ds/deepseek-v4-flash",
|
|
"deepseek/deepseek-v4-pro",
|
|
"deepseek/deepseek-v4-flash",
|
|
]) {
|
|
const out = normalizeReasoningRequest({ model, effort: "max" }) as Record<string, unknown>;
|
|
assert.equal(
|
|
out.reasoning_effort,
|
|
"max",
|
|
`${model} must reach DeepSeek's native max tier, not the down-mapped xhigh`
|
|
);
|
|
assert.deepEqual((out.reasoning as Record<string, unknown>).effort, "max");
|
|
}
|
|
});
|
|
|
|
test("A2: the provider can also be supplied explicitly (model id without prefix)", () => {
|
|
const out = normalizeReasoningRequest(
|
|
{ model: "deepseek-v4-pro", effort: "max" },
|
|
"deepseek"
|
|
) as Record<string, unknown>;
|
|
assert.equal(out.reasoning_effort, "max");
|
|
});
|
|
|
|
test("B: `max` still collapses to `xhigh` for every other provider", () => {
|
|
for (const model of ["openai/gpt-5", "anthropic/claude-opus-4-8", "z-ai/glm-5.2"]) {
|
|
const out = normalizeReasoningRequest({ model, effort: "max" }) as Record<string, unknown>;
|
|
assert.equal(out.reasoning_effort, "xhigh", `${model} must keep the canonical collapse`);
|
|
}
|
|
// The global vocabulary itself is unchanged.
|
|
assert.deepEqual([...CANONICAL_EFFORT_VALUES], ["none", "low", "medium", "high", "xhigh"]);
|
|
assert.equal(normalizeEffort("max"), "xhigh");
|
|
});
|
|
|
|
test("C: routed DeepSeek namespaces are not treated as the native provider", () => {
|
|
// These terminate at a different upstream whose effort vocabulary we do not control.
|
|
for (const model of [
|
|
"openrouter/deepseek/deepseek-v4-flash-0731",
|
|
"tllm/deepseek_v4",
|
|
"oc/deepseek-v4-flash-free",
|
|
]) {
|
|
assert.equal(isDeepSeekNativeMaxModel(null, model), false, `${model} is not native`);
|
|
const out = normalizeReasoningRequest({ model, effort: "max" }) as Record<string, unknown>;
|
|
assert.equal(out.reasoning_effort, "xhigh");
|
|
}
|
|
});
|
|
|
|
test("D: an explicit client reasoning_effort still wins over canonical effort", () => {
|
|
const out = normalizeReasoningRequest({
|
|
model: "ds/deepseek-v4-flash",
|
|
effort: "max",
|
|
reasoning_effort: "low",
|
|
}) as Record<string, unknown>;
|
|
assert.equal(out.reasoning_effort, "low", "explicit client intent must be preserved");
|
|
});
|
|
|
|
test("E: catalog effort tiers advertise `max` for native DeepSeek models only", () => {
|
|
const base = [...CANONICAL_EFFORT_VALUES];
|
|
|
|
const deepseekTiers = extendDeepSeekEffortValues("deepseek", "deepseek-v4-pro", base);
|
|
assert.ok(deepseekTiers.includes("max"), "native DeepSeek must advertise the max tier");
|
|
|
|
const otherTiers = extendDeepSeekEffortValues("openai", "gpt-5", base);
|
|
assert.ok(!otherTiers.includes("max"), "other providers must be untouched");
|
|
|
|
// Idempotent: never duplicate an already-present tier.
|
|
const twice = extendDeepSeekEffortValues("ds", "deepseek-v4-flash", deepseekTiers);
|
|
assert.equal(twice.filter((t) => t === "max").length, 1);
|
|
});
|