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.
This commit is contained in:
parent
ee3d1ce3a2
commit
eeed153e2f
6 changed files with 73 additions and 32 deletions
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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).
|
||||
|
|
|
|||
|
|
@ -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({
|
|||
|
||||
<CollapsibleSection label="Sampling" defaultOpen={true}>
|
||||
<div className="flex flex-col gap-5 pt-1">
|
||||
<ParamSlider
|
||||
label="Temperature"
|
||||
value={params.temperature}
|
||||
min={0}
|
||||
max={2}
|
||||
step={0.01}
|
||||
onChange={set("temperature")}
|
||||
info="Controls randomness. Lower values make output focused and deterministic; higher values increase variety and creativity."
|
||||
/>
|
||||
<ParamSlider
|
||||
label="Top P"
|
||||
value={params.topP}
|
||||
min={0}
|
||||
max={1}
|
||||
step={0.05}
|
||||
onChange={set("topP")}
|
||||
displayValue={params.topP === 1 ? "Off" : undefined}
|
||||
info="Nucleus sampling. Restricts choices to the smallest set of tokens whose cumulative probability reaches this threshold. 1.0 = off."
|
||||
/>
|
||||
{showTemperature ? (
|
||||
<ParamSlider
|
||||
label="Temperature"
|
||||
value={params.temperature}
|
||||
min={0}
|
||||
max={2}
|
||||
step={0.01}
|
||||
onChange={set("temperature")}
|
||||
info="Controls randomness. Lower values make output focused and deterministic; higher values increase variety and creativity."
|
||||
/>
|
||||
) : null}
|
||||
{showTopP ? (
|
||||
<ParamSlider
|
||||
label="Top P"
|
||||
value={params.topP}
|
||||
min={0}
|
||||
max={1}
|
||||
step={0.05}
|
||||
onChange={set("topP")}
|
||||
displayValue={params.topP === 1 ? "Off" : undefined}
|
||||
info="Nucleus sampling. Restricts choices to the smallest set of tokens whose cumulative probability reaches this threshold. 1.0 = off."
|
||||
/>
|
||||
) : null}
|
||||
{showTopK ? (
|
||||
<ParamSlider
|
||||
label="Top K"
|
||||
|
|
|
|||
|
|
@ -14,6 +14,13 @@
|
|||
*/
|
||||
|
||||
export interface ProviderCapabilities {
|
||||
/**
|
||||
* Temperature sampling. Reasoning-class models (OpenAI's gpt-5.x / o3 via
|
||||
* /v1/responses) reject this with `Unsupported parameter`.
|
||||
*/
|
||||
temperature: boolean;
|
||||
/** Nucleus (top_p) sampling. Same restriction as `temperature` on OpenAI. */
|
||||
topP: boolean;
|
||||
/** top-k token sampling (only Anthropic on the providers we ship). */
|
||||
topK: boolean;
|
||||
/** min-p token cutoff (no SaaS provider currently exposes this). */
|
||||
|
|
@ -38,6 +45,8 @@ export interface ProviderCapabilities {
|
|||
export const EXTERNAL_MAX_OUTPUT_TOKENS = 32768;
|
||||
|
||||
const OPENAI_COMPAT_BASE: ProviderCapabilities = {
|
||||
temperature: true,
|
||||
topP: true,
|
||||
topK: false,
|
||||
minP: false,
|
||||
repetitionPenalty: false,
|
||||
|
|
@ -45,6 +54,8 @@ const OPENAI_COMPAT_BASE: ProviderCapabilities = {
|
|||
};
|
||||
|
||||
const ALL_SUPPORTED: ProviderCapabilities = {
|
||||
temperature: true,
|
||||
topP: true,
|
||||
topK: true,
|
||||
minP: true,
|
||||
repetitionPenalty: true,
|
||||
|
|
@ -52,10 +63,13 @@ const ALL_SUPPORTED: ProviderCapabilities = {
|
|||
};
|
||||
|
||||
const PROVIDER_CAPABILITIES: Record<string, ProviderCapabilities> = {
|
||||
// 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<string, ProviderCapabilities> = {
|
|||
},
|
||||
// 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<string, ProviderCapabilities> = {
|
|||
gemini: OPENAI_COMPAT_BASE,
|
||||
// DeepSeek deprecated presence/frequency penalty in their current docs.
|
||||
deepseek: {
|
||||
temperature: true,
|
||||
topP: true,
|
||||
topK: false,
|
||||
minP: false,
|
||||
repetitionPenalty: false,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue