Regression of #371 — ResponsesModel.toString() leaks into Azure deployment URL path via PrepareRequest.modelNameOrNull()
Description
When using the Responses API (client.responses().create(...)) against an Azure OpenAI / Azure AI Foundry endpoint configured with a deployment-based (legacy-style) base URL, the generated request URL contains the toString() representation of the internal ResponsesModel wrapper instead of the plain model/deployment name.
This is the same symptom originally reported in #371 ("Malformed URL for Response API when using Azure OpenAI"), which was fixed in v0.44.2 via #387. It appears the fix did not account for the ResponsesModel union/sealed type that was introduced later (supporting a plain string, a ChatModel enum, or a "Responses-only" model), so the same failure mode has resurfaced through a different path.
Reproduction
AzureOpenAIClient client = OpenAIOkHttpClient.builder()
.baseUrl("https://<resource>.example.azure-api.net/foundry/") // deployment-based / legacy Azure URL mode
.credential(BearerTokenCredential.create(() -> "<token>"))
.build();
ResponseCreateParams params = ResponseCreateParams.builder()
.model("gpt-5.4")
.input("hello")
.build();
client.responses().create(params);
Expected URL
POST https://<resource>.example.azure-api.net/foundry/openai/deployments/gpt-5.4/responses?api-version=2024-10-21
Actual URL
POST https://<resource>.example.azure-api.net/foundry/openai/deployments/ResponsesModel%7Bstring=gpt-5.4%7D/responses?api-version=2024-10-21
URL-decoded, the deployment path segment is literally ResponsesModel{string=gpt-5.4} instead of gpt-5.4. The backend/gateway correctly rejects this with a 404 since no such deployment exists.
Root cause
com.openai.core.PrepareRequest.modelNameOrNull() uses Kotlin reflection to find a method named model on the request params, invokes it, unwraps the result if it's an Optional, and then unconditionally calls .toString() on whatever remains:
internal fun Params.modelNameOrNull(): String? {
val modelName =
try {
this::class.declaredFunctions.find { it.name == "model" }?.call(this)
} catch (_: Exception) {
null
}
return when (modelName) {
is Optional<*> -> modelName.orElse(null)?.toString()
else -> modelName?.toString()
}
}
For ResponseCreateParams, .model() returns Optional<ResponsesModel>. ResponsesModel is a union/sealed wrapper (it can hold a plain String, a ChatModel, or a ResponsesOnlyModel), and its toString() is implemented for debugging/logging purposes:
override fun toString(): String =
if (this.string != null) "ResponsesModel{string=${this.string}}"
else if (this.chat != null) "ResponsesModel{chat=${this.chat}}"
else if (this.only != null) "ResponsesModel{only=${this.only}}"
else "ResponsesModel{_unknown=${this._json}}"
Because modelNameOrNull() calls .toString() instead of resolving the wrapper via its accessors (e.g. checking isString() and calling asString(), or otherwise handling the chat/only variants), the debug-style string leaks directly into the URL path.
This is analogous to the originally-reported #371 symptom and to #492 (a similar "Optional[ ]" leak for the Azure Image Service), suggesting modelNameOrNull()'s generic reflection + toString() approach is fragile whenever a request type's model field is backed by a union/wrapper type rather than a plain String or simple enum.
Sample app
repro.zip
This Java console app reproduces a bug in the OpenAI Java SDK where the ResponsesModel.toString()
representation leaks into Azure deployment URLs instead of using the plain model name.
The app uses a local MockWebServer to capture the actual request URL generated by the SDK
when making a Responses API call against an Azure OpenAI / Azure AI Foundry endpoint configured
with a deployment-based (legacy-style) base URL. It requires no credentials and sends no
actual request to Azure.
This behavior is a regression of openai-java issue #371.
Build and Run
The reproduction is pinned to com.openai:openai-java:4.56.0 in pom.xml.
Pinning the dependency keeps the sample behavior reproducible if a future SDK version changes
the URL handling.
To build and run:
mvn clean compile exec:java
The output shows the SDK-generated request path, making the ResponsesModel leak
visible without manually constructing the URL.
Regression of #371 —
ResponsesModel.toString()leaks into Azure deployment URL path viaPrepareRequest.modelNameOrNull()Description
When using the Responses API (
client.responses().create(...)) against an Azure OpenAI / Azure AI Foundry endpoint configured with a deployment-based (legacy-style) base URL, the generated request URL contains thetoString()representation of the internalResponsesModelwrapper instead of the plain model/deployment name.This is the same symptom originally reported in #371 ("Malformed URL for Response API when using Azure OpenAI"), which was fixed in v0.44.2 via #387. It appears the fix did not account for the
ResponsesModelunion/sealed type that was introduced later (supporting a plain string, aChatModelenum, or a "Responses-only" model), so the same failure mode has resurfaced through a different path.Reproduction
Expected URL
Actual URL
URL-decoded, the deployment path segment is literally
ResponsesModel{string=gpt-5.4}instead ofgpt-5.4. The backend/gateway correctly rejects this with a 404 since no such deployment exists.Root cause
com.openai.core.PrepareRequest.modelNameOrNull()uses Kotlin reflection to find a method namedmodelon the request params, invokes it, unwraps the result if it's anOptional, and then unconditionally calls.toString()on whatever remains:For
ResponseCreateParams,.model()returnsOptional<ResponsesModel>.ResponsesModelis a union/sealed wrapper (it can hold a plainString, aChatModel, or aResponsesOnlyModel), and itstoString()is implemented for debugging/logging purposes:Because
modelNameOrNull()calls.toString()instead of resolving the wrapper via its accessors (e.g. checkingisString()and callingasString(), or otherwise handling thechat/onlyvariants), the debug-style string leaks directly into the URL path.This is analogous to the originally-reported #371 symptom and to #492 (a similar "Optional[ ]" leak for the Azure Image Service), suggesting
modelNameOrNull()'s generic reflection +toString()approach is fragile whenever a request type'smodelfield is backed by a union/wrapper type rather than a plainStringor simple enum.Sample app
repro.zip
This Java console app reproduces a bug in the OpenAI Java SDK where the
ResponsesModel.toString()representation leaks into Azure deployment URLs instead of using the plain model name.
The app uses a local
MockWebServerto capture the actual request URL generated by the SDKwhen making a Responses API call against an Azure OpenAI / Azure AI Foundry endpoint configured
with a deployment-based (legacy-style) base URL. It requires no credentials and sends no
actual request to Azure.
This behavior is a regression of openai-java issue #371.
Build and Run
The reproduction is pinned to
com.openai:openai-java:4.56.0inpom.xml.Pinning the dependency keeps the sample behavior reproducible if a future SDK version changes
the URL handling.
To build and run:
The output shows the SDK-generated request path, making the
ResponsesModelleakvisible without manually constructing the URL.