Files
OmniRoute/tests/unit/gpt5-sampling-guard.test.ts
backryun 67a0b99240 feat(providers): add GPT-5.6 model family (#6862)
* feat(providers): add GPT-5.6 model family

* fix(chatgpt-web): resume temporary chat handoffs

* fix(codex): auto-merge discovery, filter denylist, revalidate on lifecycle

Restore live/GitHub auto-merge for Codex catalogs, drop models via
explicit denylist (GPT-5.4 family), and run scrub+live re-sync once on
first-start, app upgrade, or setup completion. Success log:
kill deprecated models complete.

* fix(codex): preserve live catalog reconciliation

Expose remote-only Codex models without dropping user custom entries, and complete lifecycle revalidation only after a successful internal sync. Keep credentialed self-fetches pinned to the active dashboard listener.

---------

Co-authored-by: backryun <backryun@daonlab.local>
2026-07-12 03:30:47 -03:00

100 lines
4.0 KiB
TypeScript

/**
* GPT-5 sampling guard — `stripGpt5SamplingWhenReasoning`.
*
* GPT-5 reasoning models reject non-default `temperature`/`top_p` with HTTP 400 when a
* reasoning effort is active, but GPT-5.1+ accept them again under `reasoning_effort:"none"`
* (the default). These tests pin the conditional strip on the `openai` Chat Completions
* surface: drop sampling only when reasoning is active, never for `none` / non-gpt-5 / other
* providers. Refs: litellm#27351, Azure Foundry reasoning matrix, openai-python#2072.
*/
import test from "node:test";
import assert from "node:assert/strict";
import { stripGpt5SamplingWhenReasoning } from "../../open-sse/services/gpt5SamplingGuard.ts";
test("strips temperature+top_p for openai gpt-5.x when reasoning_effort is active", () => {
const body = {
model: "gpt-5.4",
temperature: 0.7,
top_p: 0.9,
reasoning_effort: "high",
messages: [],
};
const result = stripGpt5SamplingWhenReasoning(body, "openai", "gpt-5.4");
assert.equal(result.temperature, undefined);
assert.equal(result.top_p, undefined);
assert.equal(result.reasoning_effort, "high"); // effort itself is untouched
});
test("keeps temperature when reasoning_effort=none (gpt-5.1+ non-reasoning mode)", () => {
const body = { model: "gpt-5.4", temperature: 0.7, reasoning_effort: "none", messages: [] };
const result = stripGpt5SamplingWhenReasoning(body, "openai", "gpt-5.4");
assert.equal(result.temperature, 0.7);
});
test("keeps sampling when there is no reasoning signal (default none for gpt-5.1+)", () => {
const body = { model: "gpt-5.5", temperature: 0.5, top_p: 0.8, messages: [] };
const result = stripGpt5SamplingWhenReasoning(body, "openai", "gpt-5.5");
assert.equal(result.temperature, 0.5);
assert.equal(result.top_p, 0.8);
});
test("nested reasoning.effort active also triggers the strip", () => {
const body = { model: "gpt-5.4", top_p: 0.8, reasoning: { effort: "medium" }, messages: [] };
const result = stripGpt5SamplingWhenReasoning(body, "openai", "gpt-5.4");
assert.equal(result.top_p, undefined);
});
test("model suffix -high triggers strip; -none keeps sampling", () => {
const high = stripGpt5SamplingWhenReasoning(
{ model: "gpt-5.4-high", temperature: 0.3 },
"openai",
"gpt-5.4-high"
);
assert.equal(high.temperature, undefined);
const none = stripGpt5SamplingWhenReasoning(
{ model: "gpt-5.4-none", temperature: 0.3 },
"openai",
"gpt-5.4-none"
);
assert.equal(none.temperature, 0.3);
});
test("non-openai provider is untouched (codex is guarded by the executor allowlist)", () => {
const body = { model: "gpt-5.6-sol", temperature: 0.7, reasoning_effort: "high" };
const result = stripGpt5SamplingWhenReasoning(body, "codex", "gpt-5.6-sol");
assert.equal(result.temperature, 0.7);
});
test("non-gpt-5 openai model is untouched (e.g. gpt-4o)", () => {
const body = { model: "gpt-4o", temperature: 0.7, reasoning_effort: "high" };
const result = stripGpt5SamplingWhenReasoning(body, "openai", "gpt-4o");
assert.equal(result.temperature, 0.7);
});
test("returns the same reference when no sampling params are present", () => {
const body = { model: "gpt-5.4", reasoning_effort: "high", messages: [] };
const result = stripGpt5SamplingWhenReasoning(body, "openai", "gpt-5.4");
assert.equal(result, body);
});
test("non-string model is a no-op", () => {
const body = { temperature: 0.7, reasoning_effort: "high" };
const result = stripGpt5SamplingWhenReasoning(body, "openai", null);
assert.equal(result.temperature, 0.7);
});
test("logs the stripped params when a logger is provided", () => {
const calls: Array<[string, string]> = [];
const log = { warn: (tag: string, message: string) => calls.push([tag, message]) };
stripGpt5SamplingWhenReasoning(
{ model: "gpt-5.4", temperature: 0.7, reasoning_effort: "high" },
"openai",
"gpt-5.4",
log
);
assert.equal(calls.length, 1);
assert.equal(calls[0][0], "PARAMS");
assert.match(calls[0][1], /temperature/);
});