From eeed153e2fb7b5c7c018eb143d227dac37625077 Mon Sep 17 00:00:00 2001 From: Roland Tannous Date: Tue, 12 May 2026 13:58:37 +0400 Subject: [PATCH] studio: drop temperature/top_p for OpenAI reasoning models gpt-5.x / o3 / gpt-4.5 are reasoning-class models served via /v1/responses, and reject temperature and top_p with 'Unsupported parameter' 400s. The OpenAI registry allowlist already scopes the picker to those families, so neither knob ever applies on this branch. - external_provider._stream_openai_responses no longer puts temperature or top_p in the request body (kept on the method signature for API symmetry with the other stream methods). - ProviderCapabilities gains temperature/topP flags; OpenAI sets both to false. ChatSettingsPanel hides the sliders for OpenAI so the user does not see inert controls. - chat-adapter omits temperature/top_p from the external request body when the active provider does not advertise them. - OpenAIChatCompletionsRequest type marks both as optional, matching the new chat-adapter shape. - test_responses_request_body_uses_input_and_instructions: assertions flipped to confirm temperature / top_p are absent from the body. --- .../core/inference/external_provider.py | 12 ++++- .../test_openai_responses_translation.py | 9 ++-- .../src/features/chat/api/chat-adapter.ts | 10 ++++- .../src/features/chat/chat-settings-sheet.tsx | 45 +++++++++++-------- .../features/chat/provider-capabilities.ts | 24 ++++++++-- .../frontend/src/features/chat/types/api.ts | 5 ++- 6 files changed, 73 insertions(+), 32 deletions(-) diff --git a/studio/backend/core/inference/external_provider.py b/studio/backend/core/inference/external_provider.py index 56e4e9bc45..f18075a6a3 100644 --- a/studio/backend/core/inference/external_provider.py +++ b/studio/backend/core/inference/external_provider.py @@ -469,12 +469,20 @@ class ExternalProviderClient: if translated_parts: input_items.append({"role": role, "content": translated_parts}) + # NOTE: gpt-5.x / o3 / gpt-4.5 are reasoning-class models. They reject + # temperature and top_p with `Unsupported parameter` 400s on + # /v1/responses (and on /v1/chat/completions for the same families). + # The PROVIDER_REGISTRY['openai'] model_id_allowlist already scopes + # the picker to those families, so we never need to send sampling + # knobs here. ``reasoning.effort`` defaults to "medium" server-side + # if omitted — surface it in a future commit if a knob is wanted. + del temperature, top_p # explicit drop — params are accepted for + # API symmetry with the other stream methods but not forwarded. + body: dict[str, Any] = { "model": model, "input": input_items, "stream": True, - "temperature": temperature, - "top_p": top_p, } if instructions_parts: body["instructions"] = "\n\n".join(instructions_parts) diff --git a/studio/backend/tests/test_openai_responses_translation.py b/studio/backend/tests/test_openai_responses_translation.py index 54cda09a41..1291f72cd0 100644 --- a/studio/backend/tests/test_openai_responses_translation.py +++ b/studio/backend/tests/test_openai_responses_translation.py @@ -97,12 +97,13 @@ def test_responses_request_body_uses_input_and_instructions(monkeypatch): assert body["model"] == "gpt-5.5" assert body["instructions"] == "You are concise." assert body["input"] == [{"role": "user", "content": "Hi"}] - assert body["temperature"] == 0.5 - assert body["top_p"] == 0.9 assert body["max_output_tokens"] == 512 assert body["stream"] is True - # Responses API does not accept these — frontend caps + backend path both - # strip them; make sure we never silently forward them. + # Responses API on reasoning-class models (gpt-5.x / o3 / gpt-4.5 — the + # only OpenAI ids the registry allowlist exposes) rejects these as + # `Unsupported parameter`. Make sure we never silently forward them. + assert "temperature" not in body + assert "top_p" not in body assert "presence_penalty" not in body assert "frequency_penalty" not in body assert "top_k" not in body diff --git a/studio/frontend/src/features/chat/api/chat-adapter.ts b/studio/frontend/src/features/chat/api/chat-adapter.ts index 97a22c50bd..956dab9213 100644 --- a/studio/frontend/src/features/chat/api/chat-adapter.ts +++ b/studio/frontend/src/features/chat/api/chat-adapter.ts @@ -860,8 +860,14 @@ export function createOpenAIStreamAdapter(): ChatModelAdapter { model: externalSelection.modelId, messages: outboundMessages, stream: true, - temperature: params.temperature, - top_p: params.topP, + // Reasoning-class models (OpenAI gpt-5.x / o3) reject temperature + // and top_p; only forward when the active provider supports them. + ...(externalCapabilities?.temperature !== false + ? { temperature: params.temperature } + : {}), + ...(externalCapabilities?.topP !== false + ? { top_p: params.topP } + : {}), // Clamp to the cross-provider output cap so a maxTokens value // carried over from a local-model session does not blow past // provider limits (e.g. Claude Opus 400s on >128k). diff --git a/studio/frontend/src/features/chat/chat-settings-sheet.tsx b/studio/frontend/src/features/chat/chat-settings-sheet.tsx index 80f4b6ebdd..3c60a356cc 100644 --- a/studio/frontend/src/features/chat/chat-settings-sheet.tsx +++ b/studio/frontend/src/features/chat/chat-settings-sheet.tsx @@ -532,6 +532,9 @@ export function ChatSettingsPanel({ // is only consulted when `isExternalModel` is true. An external model with an // unknown provider falls back to the OpenAI-compat shape via // getProviderCapabilities, so these flags never undercount support. + const showTemperature = + !isExternalModel || Boolean(providerCapabilities?.temperature); + const showTopP = !isExternalModel || Boolean(providerCapabilities?.topP); const showTopK = !isExternalModel || Boolean(providerCapabilities?.topK); const showMinP = !isExternalModel || Boolean(providerCapabilities?.minP); const showRepetitionPenalty = @@ -1155,25 +1158,29 @@ export function ChatSettingsPanel({
- - + {showTemperature ? ( + + ) : null} + {showTopP ? ( + + ) : null} {showTopK ? ( = { - // OpenAI's flagship models (gpt-5.x) now require /v1/responses, and the - // Responses API drops presence/frequency penalty from the contract — see - // backend external_provider._stream_openai_responses for the proxy. + // OpenAI's flagship models (gpt-5.x / o3 / gpt-4.5) are reasoning-class + // models served via /v1/responses, which rejects temperature, top_p, and + // presence/frequency penalty. See backend + // external_provider._stream_openai_responses for the proxy. openai: { + temperature: false, + topP: false, topK: false, minP: false, repetitionPenalty: false, @@ -63,6 +77,8 @@ const PROVIDER_CAPABILITIES: Record = { }, // Anthropic's Messages API accepts top_k but not presence/frequency penalty. anthropic: { + temperature: true, + topP: true, topK: true, minP: false, repetitionPenalty: false, @@ -72,6 +88,8 @@ const PROVIDER_CAPABILITIES: Record = { gemini: OPENAI_COMPAT_BASE, // DeepSeek deprecated presence/frequency penalty in their current docs. deepseek: { + temperature: true, + topP: true, topK: false, minP: false, repetitionPenalty: false, diff --git a/studio/frontend/src/features/chat/types/api.ts b/studio/frontend/src/features/chat/types/api.ts index 97fa39065a..f9070f8a32 100644 --- a/studio/frontend/src/features/chat/types/api.ts +++ b/studio/frontend/src/features/chat/types/api.ts @@ -190,8 +190,9 @@ export interface OpenAIChatCompletionsRequest { model: string; messages: OpenAIChatMessage[]; stream: boolean; - temperature: number; - top_p: number; + /** Reasoning-class OpenAI models reject these — caller may omit. */ + temperature?: number; + top_p?: number; max_tokens: number; top_k?: number; min_p?: number;