Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 27 additions & 6 deletions backend/src/utils/llm/providers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,31 @@ function clampTemperature(model: string, requested: number): number {
return requested;
}

/**
* Detects the "this model only supports the default temperature" error.
*
* Real OpenAI populates `err.code === "unsupported_value"` and
* `err.param === "temperature"`, but many OpenAI-*compatible* endpoints only
* return the message string (e.g. "Unsupported value: 'temperature' does not
* support 0.7 with this model. Only the default (1) value is supported.")
* without those structured fields, so we fall back to matching the text.
*/
export function isTemperatureUnsupportedError(err: any): boolean {
const param = err?.param ?? err?.error?.param;
const code = err?.code ?? err?.error?.code;
if (code === "unsupported_value" && param === "temperature") return true;

const message = String(
err?.message ?? err?.error?.message ?? "",
).toLowerCase();
return (
message.includes("temperature") &&
(message.includes("does not support") ||
message.includes("only the default") ||
message.includes("unsupported value"))
);
}

function normalizeFinishReason(raw: string | null | undefined): FinishReason {
if (raw === "stop" || raw === "end_turn") return "stop";
if (raw === "tool_calls" || raw === "tool_use") return "tool_calls";
Expand Down Expand Up @@ -623,9 +648,7 @@ export async function invoke_llm(opts: InvokeOptions): Promise<InvokeResult> {
return await tryCompletion(requestedTemp);
} catch (err: any) {
const isTempUnsupported =
err?.code === "unsupported_value" &&
err?.param === "temperature" &&
requestedTemp !== 1;
requestedTemp !== 1 && isTemperatureUnsupportedError(err);

if (isTempUnsupported) {
console.warn(
Expand Down Expand Up @@ -1259,9 +1282,7 @@ export async function invoke_llm_streaming(
return await runStream(requestedTemp);
} catch (err: any) {
const isTempUnsupported =
err?.code === "unsupported_value" &&
err?.param === "temperature" &&
requestedTemp !== 1;
requestedTemp !== 1 && isTemperatureUnsupportedError(err);

if (isTempUnsupported) {
console.warn(`[inference] Retrying stream with temperature=1`);
Expand Down
51 changes: 51 additions & 0 deletions backend/tests/temperatureRetry.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { test } from "node:test";
import assert from "node:assert/strict";
import { isTemperatureUnsupportedError } from "../src/utils/llm/providers";

test("detects structured OpenAI unsupported_value/temperature error", () => {
const err = {
status: 400,
code: "unsupported_value",
param: "temperature",
message: "Unsupported value: 'temperature' does not support 0.7 with this model.",
};
assert.equal(isTemperatureUnsupportedError(err), true);
});

test("detects OpenAI-compatible endpoints that only return a message string", () => {
// Many OpenAI-compatible servers omit err.code/err.param and only set the
// message — this is the exact error users hit against such endpoints.
const err = {
status: 400,
message:
"400 Unsupported value: 'temperature' does not support 0.7 with this model. Only the default (1) value is supported.",
};
assert.equal(isTemperatureUnsupportedError(err), true);
});

test("detects nested error object shape", () => {
const err = {
error: {
code: "unsupported_value",
param: "temperature",
message: "temperature does not support 0.7",
},
};
assert.equal(isTemperatureUnsupportedError(err), true);
});

test("ignores unrelated errors", () => {
assert.equal(
isTemperatureUnsupportedError({ status: 500, message: "internal server error" }),
false,
);
assert.equal(
isTemperatureUnsupportedError({
code: "unsupported_value",
param: "top_p",
message: "Unsupported value: 'top_p'",
}),
false,
);
assert.equal(isTemperatureUnsupportedError(undefined), false);
});