diff --git a/eng/versioning/external_dependencies.txt b/eng/versioning/external_dependencies.txt
index 35ba63950967..2de6e9d3f4fa 100644
--- a/eng/versioning/external_dependencies.txt
+++ b/eng/versioning/external_dependencies.txt
@@ -36,7 +36,7 @@ com.microsoft.sqlserver:mssql-jdbc;10.2.3.jre8
com.microsoft.azure:azure-functions-maven-plugin;1.30.0
com.microsoft.azure.functions:azure-functions-java-library;2.2.0
com.mysql:mysql-connector-j;9.0.0
-com.openai:openai-java;4.6.1
+com.openai:openai-java;4.14.0
com.squareup.okhttp3:okhttp;4.12.0
commons-codec:commons-codec;1.15
commons-net:commons-net;3.9.0
diff --git a/sdk/ai/azure-ai-agents/CHANGELOG.md b/sdk/ai/azure-ai-agents/CHANGELOG.md
index a3ca8244915d..2de616879d89 100644
--- a/sdk/ai/azure-ai-agents/CHANGELOG.md
+++ b/sdk/ai/azure-ai-agents/CHANGELOG.md
@@ -7,6 +7,7 @@
- New `MemorySearchAgent` sample was added demonstrating memory search functionality
- Tests for `MemoryStoresClient` and `MemoryStoresAsyncClient`
- Various documentation updates
+- Using unified `HttpClient` setup for Azure specifics and `openai` client library wrapping methods
### Breaking Changes
@@ -16,6 +17,8 @@
### Other Changes
+- Updated version of `openai` client library to `4.14.0`
+
## 1.0.0-beta.1 (2025-11-12)
### Features Added
diff --git a/sdk/ai/azure-ai-agents/pom.xml b/sdk/ai/azure-ai-agents/pom.xml
index 378c86a00a9b..b50a8af301e6 100644
--- a/sdk/ai/azure-ai-agents/pom.xml
+++ b/sdk/ai/azure-ai-agents/pom.xml
@@ -52,7 +52,7 @@
com.openai
openai-java
- 4.6.1
+ 4.14.0
com.azure
@@ -97,7 +97,7 @@
- com.openai:openai-java:[4.6.1]
+ com.openai:openai-java:[4.14.0]
diff --git a/sdk/ai/azure-ai-agents/src/main/java/com/azure/ai/agents/implementation/http/HttpClientHelper.java b/sdk/ai/azure-ai-agents/src/main/java/com/azure/ai/agents/implementation/http/HttpClientHelper.java
index e7df9ee0c91f..8052a17e5902 100644
--- a/sdk/ai/azure-ai-agents/src/main/java/com/azure/ai/agents/implementation/http/HttpClientHelper.java
+++ b/sdk/ai/azure-ai-agents/src/main/java/com/azure/ai/agents/implementation/http/HttpClientHelper.java
@@ -28,8 +28,11 @@
import com.openai.errors.UnauthorizedException;
import com.openai.errors.UnexpectedStatusCodeException;
import com.openai.errors.UnprocessableEntityException;
+import reactor.core.publisher.Mono;
import java.io.ByteArrayOutputStream;
+import java.net.MalformedURLException;
+import java.net.URI;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
@@ -82,10 +85,14 @@ public HttpResponse execute(HttpRequest request, RequestOptions requestOptions)
Objects.requireNonNull(request, "request");
Objects.requireNonNull(requestOptions, "requestOptions");
- com.azure.core.http.HttpRequest azureRequest = buildAzureRequest(request);
-
- return new AzureHttpResponseAdapter(
- this.httpPipeline.sendSync(azureRequest, buildRequestContext(requestOptions)));
+ try {
+ com.azure.core.http.HttpRequest azureRequest = buildAzureRequest(request);
+ return new AzureHttpResponseAdapter(
+ this.httpPipeline.sendSync(azureRequest, buildRequestContext(requestOptions)));
+ } catch (MalformedURLException exception) {
+ throw new OpenAIException("Invalid URL in request: " + exception.getMessage(),
+ LOGGER.logThrowableAsError(exception));
+ }
}
@Override
@@ -98,9 +105,8 @@ public CompletableFuture executeAsync(HttpRequest request, Request
Objects.requireNonNull(request, "request");
Objects.requireNonNull(requestOptions, "requestOptions");
- final com.azure.core.http.HttpRequest azureRequest = buildAzureRequest(request);
-
- return this.httpPipeline.send(azureRequest, buildRequestContext(requestOptions))
+ return Mono.fromCallable(() -> buildAzureRequest(request))
+ .flatMap(azureRequest -> this.httpPipeline.send(azureRequest, buildRequestContext(requestOptions)))
.map(response -> (HttpResponse) new AzureHttpResponseAdapter(response))
.onErrorMap(HttpClientWrapper::mapAzureExceptionToOpenAI)
.toFuture();
@@ -163,7 +169,7 @@ private static Throwable mapAzureExceptionToOpenAI(Throwable throwable) {
} else if (throwable instanceof TimeoutException) {
return throwable;
} else {
- return new OpenAIException(throwable.getMessage(), throwable.getCause());
+ return new OpenAIException(throwable.getMessage(), throwable);
}
}
@@ -179,7 +185,8 @@ private static Headers toOpenAIHeaders(HttpHeaders azureHeaders) {
/**
* Converts the OpenAI request metadata and body into an Azure {@link com.azure.core.http.HttpRequest}.
*/
- private static com.azure.core.http.HttpRequest buildAzureRequest(HttpRequest request) {
+ private static com.azure.core.http.HttpRequest buildAzureRequest(HttpRequest request)
+ throws MalformedURLException {
HttpRequestBody requestBody = request.body();
String contentType = requestBody != null ? requestBody.contentType() : null;
BinaryData bodyData = null;
@@ -196,7 +203,7 @@ private static com.azure.core.http.HttpRequest buildAzureRequest(HttpRequest req
}
com.azure.core.http.HttpRequest azureRequest = new com.azure.core.http.HttpRequest(
- HttpMethod.valueOf(request.method().name()), OpenAiRequestUrlBuilder.buildUrl(request), headers);
+ HttpMethod.valueOf(request.method().name()), URI.create(request.url()).toURL(), headers);
if (bodyData != null) {
azureRequest.setBody(bodyData);
diff --git a/sdk/ai/azure-ai-agents/src/main/java/com/azure/ai/agents/implementation/http/OpenAiRequestUrlBuilder.java b/sdk/ai/azure-ai-agents/src/main/java/com/azure/ai/agents/implementation/http/OpenAiRequestUrlBuilder.java
deleted file mode 100644
index f4490f771020..000000000000
--- a/sdk/ai/azure-ai-agents/src/main/java/com/azure/ai/agents/implementation/http/OpenAiRequestUrlBuilder.java
+++ /dev/null
@@ -1,138 +0,0 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// Licensed under the MIT License.
-
-package com.azure.ai.agents.implementation.http;
-
-import com.azure.core.util.CoreUtils;
-import com.azure.core.util.logging.ClientLogger;
-import com.openai.core.http.HttpRequest;
-import com.openai.core.http.QueryParams;
-
-import java.net.MalformedURLException;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.net.URL;
-import java.util.List;
-import java.util.StringJoiner;
-
-/**
- * Utility methods that reconstruct the absolute {@link URL} required by the Azure pipeline from the
- * OpenAI request metadata. The builder keeps the low-level path/query handling isolated so that
- * {@link HttpClientHelper} can focus on the higher-level request mapping logic.
- * This class will be deprecated as soon as support is added in the OpenAI SDK as described in this issue:
- *
- */
-public final class OpenAiRequestUrlBuilder {
-
- private static final ClientLogger LOGGER = new ClientLogger(OpenAiRequestUrlBuilder.class);
-
- private OpenAiRequestUrlBuilder() {
- }
-
- /**
- * Builds an absolute {@link URL} using the base URL, path segments, and query parameters that are stored in the
- * OpenAI {@link HttpRequest} abstraction.
- * This method will be deprecated as soon as support is added in the OpenAI SDK as described in this issue:
- *
- *
- * @param request Source request provided by the OpenAI client.
- * @return Absolute URL that can be consumed by Azure HTTP components.
- */
- static URL buildUrl(HttpRequest request) {
- try {
- URI baseUri = URI.create(request.baseUrl());
- URL baseUrl = baseUri.toURL();
- String path = buildPath(baseUrl.getPath(), request.pathSegments());
- String query = buildQueryString(request.queryParams());
- URI resolved = new URI(baseUrl.getProtocol(), baseUrl.getUserInfo(), baseUrl.getHost(), baseUrl.getPort(),
- path, query, null);
- return resolved.toURL();
- } catch (MalformedURLException | URISyntaxException ex) {
- throw LOGGER.logThrowableAsWarning(new IllegalStateException(
- "Failed to build Azure HTTP request URL from base: " + request.baseUrl(), ex));
- }
- }
-
- /**
- * Creates a normalized path that merges the OpenAI base path with the additional path segments present on the
- * request.
- */
- private static String buildPath(String basePath, List pathSegments) {
- StringBuilder builder = new StringBuilder();
- String normalizedBasePath = normalizeBasePath(basePath);
- if (!CoreUtils.isNullOrEmpty(normalizedBasePath)) {
- builder.append(normalizedBasePath);
- }
-
- for (String segment : pathSegments) {
- if (builder.length() == 0 || builder.charAt(builder.length() - 1) != '/') {
- builder.append('/');
- }
- if (segment != null) {
- builder.append(segment);
- }
- }
-
- return builder.length() == 0 ? "/" : builder.toString();
- }
-
- /**
- * Normalizes the base path ensuring trailing slashes are removed and {@code null} inputs result in an empty path.
- */
- private static String normalizeBasePath(String basePath) {
- if (CoreUtils.isNullOrEmpty(basePath)) {
- return "";
- }
- if ("/".equals(basePath)) {
- return "";
- }
- return trimTrailingSlash(basePath);
- }
-
- /**
- * Removes the final {@code '/'} character when present so that subsequent concatenation does not duplicate
- * separators.
- */
- private static String trimTrailingSlash(String value) {
- if (value == null) {
- return null;
- }
- int length = value.length();
- if (length == 0) {
- return value;
- }
- return value.charAt(length - 1) == '/' ? value.substring(0, length - 1) : value;
- }
-
- /**
- * Converts OpenAI {@link QueryParams} into a flattened query string. Encoding is deferred to {@link URI} so we do
- * not double-encode values already escaped by upstream layers.
- */
- private static String buildQueryString(QueryParams queryParams) {
- if (queryParams == null || queryParams.isEmpty()) {
- return null;
- }
-
- StringJoiner joiner = new StringJoiner("&");
- queryParams.keys().forEach(name -> {
- List values = queryParams.values(name);
- if (values.isEmpty()) {
- joiner.add(name);
- } else {
- values.forEach(value -> joiner.add(formatQueryComponent(name, value)));
- }
- });
- String query = joiner.toString();
- return query.isEmpty() ? null : query;
- }
-
- /**
- * Formats a single query component using {@code name=value} semantics, handling parameters that omit a value.
- */
- private static String formatQueryComponent(String name, String value) {
- if (value == null) {
- return name;
- }
- return name + "=" + value;
- }
-}
diff --git a/sdk/ai/azure-ai-agents/src/test/java/com/azure/ai/agents/implementation/http/HttpClientHelperTests.java b/sdk/ai/azure-ai-agents/src/test/java/com/azure/ai/agents/implementation/http/HttpClientHelperTests.java
index 59ab92fb7cde..bf3b9a4bd77b 100644
--- a/sdk/ai/azure-ai-agents/src/test/java/com/azure/ai/agents/implementation/http/HttpClientHelperTests.java
+++ b/sdk/ai/azure-ai-agents/src/test/java/com/azure/ai/agents/implementation/http/HttpClientHelperTests.java
@@ -4,9 +4,7 @@
package com.azure.ai.agents.implementation.http;
import com.azure.core.http.HttpClient;
-import com.azure.core.http.HttpHeaderName;
import com.azure.core.http.HttpHeaders;
-import com.azure.core.http.HttpMethod;
import com.azure.core.http.HttpPipelineBuilder;
import com.azure.core.http.HttpRequest;
import com.azure.core.http.HttpResponse;
@@ -27,7 +25,6 @@
import java.util.concurrent.CompletableFuture;
import java.util.function.Function;
-import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
@@ -36,39 +33,6 @@
class HttpClientHelperTests {
- private static final HttpHeaderName REQUEST_ID_HEADER = HttpHeaderName.fromString("x-request-id");
- private static final HttpHeaderName CUSTOM_HEADER_NAME = HttpHeaderName.fromString("custom-header");
- private static final HttpHeaderName X_TEST_HEADER = HttpHeaderName.fromString("X-Test");
- private static final HttpHeaderName X_MULTI_HEADER = HttpHeaderName.fromString("X-Multi");
-
- @Test
- void executeMapsRequestAndResponse() {
- RecordingHttpClient recordingClient = new RecordingHttpClient(request -> createMockResponse(request, 201,
- new HttpHeaders().set(REQUEST_ID_HEADER, "req-123").set(CUSTOM_HEADER_NAME, "custom-value"), "pong"));
- com.openai.core.http.HttpClient openAiClient
- = HttpClientHelper.mapToOpenAIHttpClient(new HttpPipelineBuilder().httpClient(recordingClient).build());
-
- com.openai.core.http.HttpRequest openAiRequest = createOpenAiRequest();
-
- try (com.openai.core.http.HttpResponse response = openAiClient.execute(openAiRequest)) {
- HttpRequest sentRequest = recordingClient.getLastRequest();
- assertNotNull(sentRequest, "Azure HttpClient should receive a request");
- assertEquals(HttpMethod.POST, sentRequest.getHttpMethod());
- assertEquals("https://example.com/path/segment?q=a%20b", sentRequest.getUrl().toString());
- assertEquals("alpha", sentRequest.getHeaders().getValue(X_TEST_HEADER));
- assertArrayEquals(new String[] { "first", "second" }, sentRequest.getHeaders().getValues(X_MULTI_HEADER));
- assertEquals("text/plain", sentRequest.getHeaders().getValue(HttpHeaderName.CONTENT_TYPE));
- assertEquals("payload", new String(sentRequest.getBodyAsBinaryData().toBytes(), StandardCharsets.UTF_8));
-
- assertEquals(201, response.statusCode());
- assertEquals("req-123", response.requestId().orElseThrow(() -> new AssertionError("Missing request id")));
- assertEquals("custom-value", response.headers().values("custom-header").get(0));
- assertEquals("pong", new String(readAllBytes(response.body()), StandardCharsets.UTF_8));
- } catch (Exception e) {
- fail("Exception thrown while reading response", e);
- }
- }
-
@Test
void executeAsyncCompletesSuccessfully() {
RecordingHttpClient recordingClient
diff --git a/sdk/ai/azure-ai-projects/CHANGELOG.md b/sdk/ai/azure-ai-projects/CHANGELOG.md
index 52515ee47e88..24f3793a26a4 100644
--- a/sdk/ai/azure-ai-projects/CHANGELOG.md
+++ b/sdk/ai/azure-ai-projects/CHANGELOG.md
@@ -10,6 +10,8 @@
### Other Changes
+- Updated version of `openai` client library to `4.14.0`
+
## 1.0.0-beta.3 (2025-11-12)
### Features Added
diff --git a/sdk/ai/azure-ai-projects/pom.xml b/sdk/ai/azure-ai-projects/pom.xml
index b58668f4d6b9..c7647d97cdb1 100644
--- a/sdk/ai/azure-ai-projects/pom.xml
+++ b/sdk/ai/azure-ai-projects/pom.xml
@@ -77,7 +77,7 @@ Code generated by Microsoft (R) TypeSpec Code Generator.
com.openai
openai-java
- 4.6.1
+ 4.14.0
@@ -106,7 +106,7 @@ Code generated by Microsoft (R) TypeSpec Code Generator.
- com.openai:openai-java:[4.6.1]
+ com.openai:openai-java:[4.14.0]
diff --git a/sdk/openai/azure-ai-openai-stainless/pom.xml b/sdk/openai/azure-ai-openai-stainless/pom.xml
index c5ef78dc4142..45eb20131aab 100644
--- a/sdk/openai/azure-ai-openai-stainless/pom.xml
+++ b/sdk/openai/azure-ai-openai-stainless/pom.xml
@@ -57,7 +57,7 @@
com.openai
openai-java
- 4.6.1
+ 4.14.0
@@ -132,7 +132,7 @@
- com.openai:openai-java:[4.6.1]
+ com.openai:openai-java:[4.14.0]