Skip to content

Commit 96e2612

Browse files
jpalvarezlCopilotCopilotsrnagar
authored
OpenAI.HttpClient to Azure.HttpClient mapper for test instrumentation (#47416)
* Porting over changes from private repo * formatting * Update sdk/ai/azure-ai-agents/src/main/java/com/azure/ai/agents/implementation/http/OpenAiRequestUrlBuilder.java Co-authored-by: Copilot <[email protected]> * Update sdk/ai/azure-ai-agents/src/main/java/com/azure/ai/agents/implementation/http/PolicyDecoratingHttpClient.java Co-authored-by: Copilot <[email protected]> * Formatting mismatch * Add async and error propagation test coverage for PolicyDecoratingHttpClient (#47429) * Initial plan * Add async and error propagation test coverage for PolicyDecoratingHttpClient Co-authored-by: jpalvarezl <[email protected]> --------- Co-authored-by: copilot-swe-agent[bot] <[email protected]> Co-authored-by: jpalvarezl <[email protected]> Co-authored-by: Jose Alvarez <[email protected]> * Add error handling tests for HttpClientHelper (#47428) * Initial plan * Add error handling tests for HttpClientHelper Added comprehensive error handling test cases including: - Null request body handling - IOException during body buffering - Malformed URLs - Async execution failures Co-authored-by: jpalvarezl <[email protected]> * Address code review feedback on error handling tests - Simplify FailingHttpRequestBody to directly throw UncheckedIOException - Improve assertion clarity in body buffering test - Remove fragile message checking in malformed URL test Co-authored-by: jpalvarezl <[email protected]> --------- Co-authored-by: copilot-swe-agent[bot] <[email protected]> Co-authored-by: jpalvarezl <[email protected]> Co-authored-by: Jose Alvarez <[email protected]> * Merged and improved assertions * More PR feedback * More PR feedback * More PR feedback * lazy mapping of types for the AzureHttpResponseAdapter * Simplified mapping * Always using default httpPipeline * Using bare minimum code * WIP: removing redundant code. Using default httpPipeline * fix async tests * Code style checks * Added recording for async test * Enabled sync tests and update assets * Updated test suite and restored value for context config * Using latest version of azure-core-test * Forwarding request timeout and adding more custom machters for tests * reassign addData result * WIP: timeout tests * Timeout tests are run only in Live test mode * Test asset update * Exceptions are allowed to propagate upwards * Error mapping in place * reformat * Format * Fixed timeout tests * PR feedback 1st round * PR feedback round 2: pom updates * dependency tag fixed and disabled test * Disabled flacky timeout tests * Disabled one more test --------- Co-authored-by: Copilot <[email protected]> Co-authored-by: Copilot <[email protected]> Co-authored-by: jpalvarezl <[email protected]> Co-authored-by: Srikanta <[email protected]>
1 parent 70079af commit 96e2612

File tree

12 files changed

+856
-27
lines changed

12 files changed

+856
-27
lines changed

eng/versioning/version_client.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -547,6 +547,7 @@ io.clientcore:optional-dependency-tests;1.0.0-beta.1;1.0.0-beta.1
547547

548548
unreleased_com.azure:azure-data-appconfiguration;1.9.0-beta.1
549549
unreleased_com.azure.v2:azure-core;2.0.0-beta.1
550+
unreleased_com.azure:azure-core-test;1.27.0-beta.14
550551
unreleased_com.azure.v2:azure-identity;2.0.0-beta.1
551552
unreleased_com.azure.v2:azure-data-appconfiguration;2.0.0-beta.1
552553
unreleased_io.clientcore:http-netty4;1.0.0-beta.1

sdk/agrifood/azure-verticals-agrifood-farming/README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,8 +103,9 @@ Farm hierarchy is a collection of below entities.
103103

104104
```java readme-sample-createFarmHierarchy
105105
// Create Party
106-
JSONObject object = new JSONObject().appendField("name", "party1");
107-
BinaryData party = BinaryData.fromObject(object);
106+
Map<String, String> partyData = new HashMap<>();
107+
partyData.put("name", "party1");
108+
BinaryData party = BinaryData.fromObject(partyData);
108109
partiesClient.createOrUpdateWithResponse("contoso-party", party, null).block();
109110

110111
// Get Party

sdk/ai/azure-ai-agents/assets.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@
22
"AssetsRepo": "Azure/azure-sdk-assets",
33
"AssetsRepoPrefixPath": "java",
44
"TagPrefix": "java/ai/azure-ai-agents",
5-
"Tag": "java/ai/azure-ai-agents_ca2ca780eb"
5+
"Tag": "java/ai/azure-ai-agents_3f32cd8dff"
66
}

sdk/ai/azure-ai-agents/pom.xml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,13 @@
7575
<dependency>
7676
<groupId>com.azure</groupId>
7777
<artifactId>azure-core-test</artifactId>
78-
<version>1.27.0-beta.13</version> <!-- {x-version-update;com.azure:azure-core-test;dependency} -->
78+
<version>1.27.0-beta.14</version> <!-- {x-version-update;unreleased_com.azure:azure-core-test;dependency} -->
79+
<scope>test</scope>
80+
</dependency>
81+
<dependency>
82+
<groupId>com.azure</groupId>
83+
<artifactId>azure-core-http-okhttp</artifactId>
84+
<version>1.13.2</version> <!-- {x-version-update;com.azure:azure-core-http-okhttp;dependency} -->
7985
<scope>test</scope>
8086
</dependency>
8187

sdk/ai/azure-ai-agents/src/main/java/com/azure/ai/agents/AgentsClientBuilder.java

Lines changed: 18 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
import com.azure.ai.agents.implementation.AgentsClientImpl;
77
import com.azure.ai.agents.implementation.TokenUtils;
8+
import com.azure.ai.agents.implementation.http.HttpClientHelper;
89
import com.azure.core.annotation.Generated;
910
import com.azure.core.annotation.ServiceClientBuilder;
1011
import com.azure.core.client.traits.ConfigurationTrait;
@@ -32,7 +33,6 @@
3233
import com.azure.core.util.ClientOptions;
3334
import com.azure.core.util.Configuration;
3435
import com.azure.core.util.CoreUtils;
35-
import com.azure.core.util.UserAgentUtil;
3636
import com.azure.core.util.builder.ClientBuilderUtil;
3737
import com.azure.core.util.logging.ClientLogger;
3838
import com.azure.core.util.serializer.JacksonAdapter;
@@ -41,7 +41,7 @@
4141
import com.openai.client.okhttp.OpenAIOkHttpClient;
4242
import com.openai.client.okhttp.OpenAIOkHttpClientAsync;
4343
import com.openai.credential.BearerTokenCredential;
44-
import java.time.Duration;
44+
4545
import java.util.ArrayList;
4646
import java.util.List;
4747
import java.util.Map;
@@ -328,7 +328,9 @@ private HttpPipeline createHttpPipeline() {
328328
* @return an instance of ConversationsAsyncClient.
329329
*/
330330
public ConversationsAsyncClient buildConversationsAsyncClient() {
331-
return new ConversationsAsyncClient(getOpenAIAsyncClientBuilder().build());
331+
return new ConversationsAsyncClient(getOpenAIAsyncClientBuilder().build()
332+
.withOptions(optionBuilder -> optionBuilder
333+
.httpClient(HttpClientHelper.mapToOpenAIHttpClient(createHttpPipeline()))));
332334
}
333335

334336
/**
@@ -337,7 +339,9 @@ public ConversationsAsyncClient buildConversationsAsyncClient() {
337339
* @return an instance of ConversationsClient.
338340
*/
339341
public ConversationsClient buildConversationsClient() {
340-
return new ConversationsClient(getOpenAIClientBuilder().build());
342+
return new ConversationsClient(getOpenAIClientBuilder().build()
343+
.withOptions(optionBuilder -> optionBuilder
344+
.httpClient(HttpClientHelper.mapToOpenAIHttpClient(createHttpPipeline()))));
341345
}
342346

343347
/**
@@ -346,8 +350,9 @@ public ConversationsClient buildConversationsClient() {
346350
* @return an instance of ResponsesClient
347351
*/
348352
public ResponsesClient buildResponsesClient() {
349-
OpenAIOkHttpClient.Builder builder = getOpenAIClientBuilder();
350-
return new ResponsesClient(builder.build());
353+
return new ResponsesClient(getOpenAIClientBuilder().build()
354+
.withOptions(optionBuilder -> optionBuilder
355+
.httpClient(HttpClientHelper.mapToOpenAIHttpClient(createHttpPipeline()))));
351356
}
352357

353358
/**
@@ -356,20 +361,22 @@ public ResponsesClient buildResponsesClient() {
356361
* @return an instance of ResponsesAsyncClient
357362
*/
358363
public ResponsesAsyncClient buildResponsesAsyncClient() {
359-
return new ResponsesAsyncClient(getOpenAIAsyncClientBuilder().build());
364+
return new ResponsesAsyncClient(getOpenAIAsyncClientBuilder().build()
365+
.withOptions(optionBuilder -> optionBuilder
366+
.httpClient(HttpClientHelper.mapToOpenAIHttpClient(createHttpPipeline()))));
360367
}
361368

362369
private OpenAIOkHttpClient.Builder getOpenAIClientBuilder() {
363370
OpenAIOkHttpClient.Builder builder = OpenAIOkHttpClient.builder()
364371
.credential(
365372
BearerTokenCredential.create(TokenUtils.getBearerTokenSupplier(this.tokenCredential, DEFAULT_SCOPES)));
366373
builder.baseUrl(this.endpoint + (this.endpoint.endsWith("/") ? "openai" : "/openai"));
367-
builder.replaceHeaders("User-Agent", getUserAgent());
368374
if (this.serviceVersion != null) {
369375
builder.azureServiceVersion(AzureOpenAIServiceVersion.fromString(this.serviceVersion.getVersion()));
370376
builder.azureUrlPathMode(AzureUrlPathMode.UNIFIED);
371377
}
372-
builder.timeout(Duration.ofSeconds(30));
378+
// We set the builder retries to 0 to avoid conflicts with the retry policy added through the HttpPipeline.
379+
builder.maxRetries(0);
373380
return builder;
374381
}
375382

@@ -378,24 +385,15 @@ private OpenAIOkHttpClientAsync.Builder getOpenAIAsyncClientBuilder() {
378385
.credential(
379386
BearerTokenCredential.create(TokenUtils.getBearerTokenSupplier(this.tokenCredential, DEFAULT_SCOPES)));
380387
builder.baseUrl(this.endpoint + (this.endpoint.endsWith("/") ? "openai" : "/openai"));
381-
builder.replaceHeaders("User-Agent", getUserAgent());
382388
if (this.serviceVersion != null) {
383389
builder.azureServiceVersion(AzureOpenAIServiceVersion.fromString(this.serviceVersion.getVersion()));
384390
builder.azureUrlPath(AzureUrlPathMode.UNIFIED);
385391
}
386-
builder.timeout(Duration.ofSeconds(30));
392+
// We set the builder retries to 0 to avoid conflicts with the retry policy added through the HttpPipeline.
393+
builder.maxRetries(0);
387394
return builder;
388395
}
389396

390-
private String getUserAgent() {
391-
HttpLogOptions localHttpLogOptions = this.httpLogOptions == null ? new HttpLogOptions() : this.httpLogOptions;
392-
ClientOptions localClientOptions = this.clientOptions == null ? new ClientOptions() : this.clientOptions;
393-
String sdkName = PROPERTIES.getOrDefault(SDK_NAME, "UnknownName");
394-
String sdkVersion = PROPERTIES.getOrDefault(SDK_VERSION, "UnknownVersion");
395-
String applicationId = CoreUtils.getApplicationId(localClientOptions, localHttpLogOptions);
396-
return UserAgentUtil.toUserAgentString(applicationId, sdkName, sdkVersion, configuration);
397-
}
398-
399397
private static final ClientLogger LOGGER = new ClientLogger(AgentsClientBuilder.class);
400398

401399
/**
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
package com.azure.ai.agents.implementation.http;
5+
6+
import com.azure.core.http.HttpHeader;
7+
import com.azure.core.http.HttpHeaders;
8+
import com.azure.core.util.logging.ClientLogger;
9+
import com.openai.core.http.Headers;
10+
import com.openai.core.http.HttpResponse;
11+
12+
import java.io.InputStream;
13+
14+
/**
15+
* Adapter that exposes an Azure {@link com.azure.core.http.HttpResponse} as an OpenAI {@link HttpResponse}. This keeps
16+
* the translation logic encapsulated so response handling elsewhere can remain framework agnostic.
17+
*/
18+
final class AzureHttpResponseAdapter implements HttpResponse {
19+
20+
private static final ClientLogger LOGGER = new ClientLogger(AzureHttpResponseAdapter.class);
21+
22+
private final com.azure.core.http.HttpResponse azureResponse;
23+
24+
/**
25+
* Creates a new adapter instance for the provided Azure response.
26+
*
27+
* @param azureResponse Response returned by the Azure pipeline.
28+
*/
29+
AzureHttpResponseAdapter(com.azure.core.http.HttpResponse azureResponse) {
30+
this.azureResponse = azureResponse;
31+
}
32+
33+
@Override
34+
public int statusCode() {
35+
return azureResponse.getStatusCode();
36+
}
37+
38+
@Override
39+
public Headers headers() {
40+
return toOpenAiHeaders(azureResponse.getHeaders());
41+
}
42+
43+
@Override
44+
public InputStream body() {
45+
return azureResponse.getBodyAsBinaryData().toStream();
46+
}
47+
48+
@Override
49+
public void close() {
50+
azureResponse.close();
51+
}
52+
53+
/**
54+
* Copies headers from the Azure response into the immutable OpenAI {@link Headers} collection.
55+
*/
56+
private static Headers toOpenAiHeaders(HttpHeaders httpHeaders) {
57+
Headers.Builder builder = Headers.builder();
58+
for (HttpHeader header : httpHeaders) {
59+
builder.put(header.getName(), header.getValuesList());
60+
}
61+
return builder.build();
62+
}
63+
}

0 commit comments

Comments
 (0)