From dddcdecc65f97026cf75e4b593bc5dcb06ba3c40 Mon Sep 17 00:00:00 2001 From: Leon Zandman Date: Thu, 3 Sep 2026 16:20:37 +0200 Subject: [PATCH] Fix for #967: Resolve ResponsesModel union to plain model name in URL path `PrepareRequest.modelNameOrNull()` called `toString()` on whatever `model()` returned, so a `ResponsesModel` leaked its debug form into the Azure deployment path (`ResponsesModel{string=gpt-5.4}` instead of `gpt-5.4`). Resolve the union through its visitor and add tests for the string, chat, and responses-only variants. --- .../kotlin/com/openai/core/PrepareRequest.kt | 29 +++++++++++++++++-- .../com/openai/core/PrepareRequestTest.kt | 29 +++++++++++++++++++ 2 files changed, 55 insertions(+), 3 deletions(-) diff --git a/openai-java-core/src/main/kotlin/com/openai/core/PrepareRequest.kt b/openai-java-core/src/main/kotlin/com/openai/core/PrepareRequest.kt index 81fee2787..28985484d 100644 --- a/openai-java-core/src/main/kotlin/com/openai/core/PrepareRequest.kt +++ b/openai-java-core/src/main/kotlin/com/openai/core/PrepareRequest.kt @@ -5,6 +5,8 @@ package com.openai.core import com.openai.azure.addPathSegmentsForAzure import com.openai.azure.replaceBearerTokenForAzure import com.openai.core.http.HttpRequest +import com.openai.models.ChatModel +import com.openai.models.ResponsesModel import java.util.Optional import java.util.concurrent.CompletableFuture import kotlin.reflect.full.declaredFunctions @@ -49,8 +51,29 @@ internal fun Params.modelNameOrNull(): String? { null } - return when (modelName) { - is Optional<*> -> modelName.orElse(null)?.toString() - else -> modelName?.toString() + val unwrappedModelName = + when (modelName) { + is Optional<*> -> modelName.orElse(null) + else -> modelName + } + + return when (unwrappedModelName) { + // The `toString()` of a union type is a debug representation, so resolve its variant. + is ResponsesModel -> unwrappedModelName.modelNameOrNull() + else -> unwrappedModelName?.toString() } } + +private fun ResponsesModel.modelNameOrNull(): String? = + accept( + object : ResponsesModel.Visitor { + override fun visitString(string: String): String = string + + override fun visitChat(chat: ChatModel): String = chat.toString() + + override fun visitOnly(only: ResponsesModel.ResponsesOnlyModel): String = + only.toString() + + override fun unknown(json: JsonValue?): String? = null + } + ) diff --git a/openai-java-core/src/test/kotlin/com/openai/core/PrepareRequestTest.kt b/openai-java-core/src/test/kotlin/com/openai/core/PrepareRequestTest.kt index fa7d9ff62..feea56450 100644 --- a/openai-java-core/src/test/kotlin/com/openai/core/PrepareRequestTest.kt +++ b/openai-java-core/src/test/kotlin/com/openai/core/PrepareRequestTest.kt @@ -2,9 +2,12 @@ package com.openai.core import com.openai.core.http.Headers import com.openai.core.http.QueryParams +import com.openai.models.ChatModel +import com.openai.models.ResponsesModel import com.openai.models.chat.completions.ChatCompletionListParams import com.openai.models.embeddings.EmbeddingCreateParams import com.openai.models.embeddings.EmbeddingModel +import com.openai.models.responses.ResponseCreateParams import com.openai.models.uploads.UploadCancelParams import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThatNoException @@ -106,4 +109,30 @@ internal class PrepareRequestTest { assertThatNoException().isThrownBy { params.modelNameOrNull() } assertThat(params.modelNameOrNull()).isNull() } + + @Test + fun modelUnionStringNotNull() { + val params = ResponseCreateParams.builder().model("my-model").input("Hello, world!").build() + + assertThat(params.modelNameOrNull()).isEqualTo("my-model") + } + + @Test + fun modelUnionChatNotNull() { + val params = + ResponseCreateParams.builder().model(ChatModel.GPT_4O).input("Hello, world!").build() + + assertThat(params.modelNameOrNull()).isEqualTo("gpt-4o") + } + + @Test + fun modelUnionOnlyNotNull() { + val params = + ResponseCreateParams.builder() + .model(ResponsesModel.ResponsesOnlyModel.O1_PRO) + .input("Hello, world!") + .build() + + assertThat(params.modelNameOrNull()).isEqualTo("o1-pro") + } }