Regression of #371 — `ResponsesModel.toString()` leaks into Azure deployment URL path via `PrepareRequest.modelNameOrNull()`
Nobody has claimed this yet.
- Dominant language
- Kotlin
- Stars
- 1.5k
- Forks
- 264
- Avg merge
- 9h 46m
- Merged PRs (30d)
- 96
Description
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
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.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start at PrepareRequest.modelNameOrNull() and trace how ResponseCreateParams.model() returns and represents ResponsesModel during Azure URL construction. Run the pinned sample with mvn clean compile exec:java to observe the generated path; done means the deployment segment uses gpt-5.4 rather than the ResponsesModel wrapper representation.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- azure, java, kotlin
- Domain
- api, backend
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 68/100