From aaa2a6b67d951ac1bedf4605c6df5601dbdb0b0f Mon Sep 17 00:00:00 2001 From: Mark Su Date: Tue, 26 May 2026 13:33:02 -0700 Subject: [PATCH 1/3] fixed openAI wrong max token request key --- .../Providers/OpenAI/OpenAIProvider+Helpers.swift | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Sources/Conduit/Providers/OpenAI/OpenAIProvider+Helpers.swift b/Sources/Conduit/Providers/OpenAI/OpenAIProvider+Helpers.swift index f6c8c11..94e7279 100644 --- a/Sources/Conduit/Providers/OpenAI/OpenAIProvider+Helpers.swift +++ b/Sources/Conduit/Providers/OpenAI/OpenAIProvider+Helpers.swift @@ -125,7 +125,9 @@ extension OpenAIProvider { // Add generation config if let maxTokens = config.maxTokens { - body["max_tokens"] = maxTokens + // o-series reasoning models and gpt-5+ reject "max_tokens"; use the newer key. + let tokenKey = requiresMaxCompletionTokens(model: model) ? "max_completion_tokens" : "max_tokens" + body[tokenKey] = maxTokens } body["temperature"] = config.temperature.openAIJSONNumber @@ -576,6 +578,12 @@ extension OpenAIProvider { } } + /// Returns true for models that require `max_completion_tokens` instead of `max_tokens`. + private nonisolated func requiresMaxCompletionTokens(model: ModelIdentifier) -> Bool { + let id = model.rawValue + return id.hasPrefix("o1") || id.hasPrefix("o3") || id.hasPrefix("o4") || id.hasPrefix("gpt-5") + } + /// Serializes reasoning configuration. private nonisolated func serializeReasoningConfig(_ config: ReasoningConfig) -> [String: Any] { var result: [String: Any] = [:] From d0d2b18d92d69dddeeddf0f3135f3db675512d35 Mon Sep 17 00:00:00 2001 From: Mark Su Date: Tue, 26 May 2026 13:40:56 -0700 Subject: [PATCH 2/3] fixed openAI request of temperature not supported changes in higher models --- .../OpenAI/OpenAIProvider+Helpers.swift | 26 +++++++++++-------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/Sources/Conduit/Providers/OpenAI/OpenAIProvider+Helpers.swift b/Sources/Conduit/Providers/OpenAI/OpenAIProvider+Helpers.swift index 94e7279..2fcad88 100644 --- a/Sources/Conduit/Providers/OpenAI/OpenAIProvider+Helpers.swift +++ b/Sources/Conduit/Providers/OpenAI/OpenAIProvider+Helpers.swift @@ -130,19 +130,22 @@ extension OpenAIProvider { body[tokenKey] = maxTokens } - body["temperature"] = config.temperature.openAIJSONNumber - body["top_p"] = config.topP.openAIJSONNumber + // o-series and gpt-5+ only accept default values for these sampling params; omit them entirely. + if !requiresMaxCompletionTokens(model: model) { + body["temperature"] = config.temperature.openAIJSONNumber + body["top_p"] = config.topP.openAIJSONNumber - if let topK = config.topK { - body["top_k"] = topK - } + if let topK = config.topK { + body["top_k"] = topK + } - if config.frequencyPenalty != 0 { - body["frequency_penalty"] = config.frequencyPenalty.openAIJSONNumber - } + if config.frequencyPenalty != 0 { + body["frequency_penalty"] = config.frequencyPenalty.openAIJSONNumber + } - if config.presencePenalty != 0 { - body["presence_penalty"] = config.presencePenalty.openAIJSONNumber + if config.presencePenalty != 0 { + body["presence_penalty"] = config.presencePenalty.openAIJSONNumber + } } if !config.stopSequences.isEmpty { @@ -578,7 +581,8 @@ extension OpenAIProvider { } } - /// Returns true for models that require `max_completion_tokens` instead of `max_tokens`. + /// Returns true for models (o-series, gpt-5+) that require `max_completion_tokens` + /// and reject non-default sampling params (temperature, top_p, penalties). private nonisolated func requiresMaxCompletionTokens(model: ModelIdentifier) -> Bool { let id = model.rawValue return id.hasPrefix("o1") || id.hasPrefix("o3") || id.hasPrefix("o4") || id.hasPrefix("gpt-5") From f51fc102ee8a0b0bde2b4df3247c8c327fcd2030 Mon Sep 17 00:00:00 2001 From: Mark Su Date: Wed, 27 May 2026 10:00:18 -0700 Subject: [PATCH 3/3] fixed missed requiresMaxCompletionTokens check in buildResponsesRequestBody - add debug when fields are ignore due to model check - strip away provider name before model check --- .../OpenAI/OpenAIProvider+Helpers.swift | 24 +++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/Sources/Conduit/Providers/OpenAI/OpenAIProvider+Helpers.swift b/Sources/Conduit/Providers/OpenAI/OpenAIProvider+Helpers.swift index 2fcad88..b04ec03 100644 --- a/Sources/Conduit/Providers/OpenAI/OpenAIProvider+Helpers.swift +++ b/Sources/Conduit/Providers/OpenAI/OpenAIProvider+Helpers.swift @@ -131,7 +131,13 @@ extension OpenAIProvider { } // o-series and gpt-5+ only accept default values for these sampling params; omit them entirely. - if !requiresMaxCompletionTokens(model: model) { + if requiresMaxCompletionTokens(model: model) { + let defaults = GenerateConfig.default + if config.temperature != defaults.temperature || config.topP != defaults.topP + || config.frequencyPenalty != defaults.frequencyPenalty || config.presencePenalty != defaults.presencePenalty { + logger.debug("Model '\(model.rawValue)' does not support custom sampling parameters; temperature, top_p, and penalties will be ignored.") + } + } else { body["temperature"] = config.temperature.openAIJSONNumber body["top_p"] = config.topP.openAIJSONNumber @@ -261,8 +267,16 @@ extension OpenAIProvider { if let maxTokens = config.maxTokens { body["max_output_tokens"] = maxTokens } - body["temperature"] = config.temperature.openAIJSONNumber - body["top_p"] = config.topP.openAIJSONNumber + + if requiresMaxCompletionTokens(model: model) { + let defaults = GenerateConfig.default + if config.temperature != defaults.temperature || config.topP != defaults.topP { + logger.debug("Model '\(model.rawValue)' does not support custom sampling parameters; temperature and top_p will be ignored.") + } + } else { + body["temperature"] = config.temperature.openAIJSONNumber + body["top_p"] = config.topP.openAIJSONNumber + } if !config.stopSequences.isEmpty { body["stop"] = config.stopSequences @@ -584,7 +598,9 @@ extension OpenAIProvider { /// Returns true for models (o-series, gpt-5+) that require `max_completion_tokens` /// and reject non-default sampling params (temperature, top_p, penalties). private nonisolated func requiresMaxCompletionTokens(model: ModelIdentifier) -> Bool { - let id = model.rawValue + let raw = model.rawValue + // Strip optional "provider/" prefix used by OpenRouter (e.g. "openai/gpt-5-mini" → "gpt-5-mini"). + let id = raw.firstIndex(of: "/").map { String(raw[raw.index(after: $0)...]) } ?? raw return id.hasPrefix("o1") || id.hasPrefix("o3") || id.hasPrefix("o4") || id.hasPrefix("gpt-5") }