From e3874b61a8918183a1421d5396819976fb0132af Mon Sep 17 00:00:00 2001 From: Jose Alvarez Date: Fri, 22 May 2026 11:42:37 +0200 Subject: [PATCH 1/6] Add protocol methods to ResponsesClient and ResponsesAsyncClient Adds BinaryData + com.openai.core.RequestOptions protocol-style methods on the Responses sync and async clients that delegate to the openai-java ResponseService.withRawResponse() / ResponseServiceAsync.withRawResponse() surface and return the openai-java raw HTTP response types directly. Specifically: - createResponseWithResponse / createResponseStreamWithResponse - getResponseWithResponse - deleteResponseWithResponse - cancelResponseWithResponse The async variants wrap the underlying CompletableFuture in Mono.fromFuture(...). All methods continue to flow through the Azure HTTP pipeline via HttpClientHelper.mapToOpenAIHttpClient. Adds OpenAIJsonHelper.jsonBodyToValueMap(BinaryData) helper used by the create methods to forward the raw JSON body verbatim through ResponseCreateParams.Builder.additionalBodyProperties. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- sdk/ai/azure-ai-agents/CHANGELOG.md | 2 + .../azure/ai/agents/ResponsesAsyncClient.java | 130 ++++++++++++++++++ .../com/azure/ai/agents/ResponsesClient.java | 127 +++++++++++++++++ .../implementation/OpenAIJsonHelper.java | 39 +++++- 4 files changed, 291 insertions(+), 7 deletions(-) diff --git a/sdk/ai/azure-ai-agents/CHANGELOG.md b/sdk/ai/azure-ai-agents/CHANGELOG.md index 6a3c9a299b51..eea929205c6a 100644 --- a/sdk/ai/azure-ai-agents/CHANGELOG.md +++ b/sdk/ai/azure-ai-agents/CHANGELOG.md @@ -4,6 +4,8 @@ ### Features Added +- Added protocol-style methods on `ResponsesClient` and `ResponsesAsyncClient` that accept a raw JSON request body (`BinaryData`) and a `com.openai.core.RequestOptions`, and return the openai-java raw HTTP response (`HttpResponseFor`, `HttpResponseFor>`, and `HttpResponse`). New methods: `createResponseWithResponse`, `createResponseStreamWithResponse`, `getResponseWithResponse`, `deleteResponseWithResponse`, and `cancelResponseWithResponse`. These delegate to the underlying openai-java `ResponseService.withRawResponse()` surface and continue to flow through the Azure HTTP pipeline. + ### Breaking Changes ### Bugs Fixed diff --git a/sdk/ai/azure-ai-agents/src/main/java/com/azure/ai/agents/ResponsesAsyncClient.java b/sdk/ai/azure-ai-agents/src/main/java/com/azure/ai/agents/ResponsesAsyncClient.java index 3ebcace05ea9..a783c71712c6 100644 --- a/sdk/ai/azure-ai-agents/src/main/java/com/azure/ai/agents/ResponsesAsyncClient.java +++ b/sdk/ai/azure-ai-agents/src/main/java/com/azure/ai/agents/ResponsesAsyncClient.java @@ -10,8 +10,13 @@ import com.azure.core.annotation.ReturnType; import com.azure.core.annotation.ServiceClient; import com.azure.core.annotation.ServiceMethod; +import com.azure.core.util.BinaryData; import com.openai.client.OpenAIClientAsync; import com.openai.core.JsonValue; +import com.openai.core.RequestOptions; +import com.openai.core.http.HttpResponse; +import com.openai.core.http.HttpResponseFor; +import com.openai.core.http.StreamResponse; import com.openai.models.responses.Response; import com.openai.models.responses.ResponseCreateParams; import com.openai.models.responses.ResponseStreamEvent; @@ -84,4 +89,129 @@ public Flux createStreamingAzureResponse(AzureCreateRespons params.additionalBodyProperties(additionalBodyProperties); return StreamingUtils.toFlux(this.responseServiceAsync.createStreaming(params.build())); } + + /** + * Creates a response from a raw JSON request body and returns the raw HTTP response. + * + *

This protocol method delegates to the OpenAI Java SDK's + * {@link ResponseServiceAsync.WithRawResponse#create(ResponseCreateParams, RequestOptions)}. The + * {@code createResponseRequest} payload is forwarded verbatim as the request body, so callers can + * include Azure-specific extensions (such as + * {@link com.azure.ai.agents.models.AgentReference}) without going through the strongly-typed + * {@link ResponseCreateParams.Builder}.

+ * + *

The returned {@link HttpResponseFor} exposes the status code, headers, and the raw + * response stream via {@code body()}, or the typed {@link Response} via {@code parse()}. Only + * one of {@code body()} or {@code parse()} may be invoked per response, and the caller must + * close the response (e.g. via try-with-resources) to release the underlying connection.

+ * + *

Note: the second parameter is the openai-java {@link RequestOptions} (not the azure-core + * type) so that the OpenAI-supported options (timeout, response validation) translate + * faithfully. Additional headers or query parameters must be supplied via the OpenAI request + * builder pattern (e.g. by using {@link #createAzureResponse} for fully-typed requests).

+ * + * @param createResponseRequest the JSON body representing the create-response request; must be a JSON object. + * @param requestOptions optional OpenAI request options; pass {@code null} to use the defaults. + * @return a {@link Mono} emitting the raw HTTP response, parseable as a {@link Response}. + */ + @ServiceMethod(returns = ReturnType.SINGLE) + public Mono> createResponseWithResponse(BinaryData createResponseRequest, + RequestOptions requestOptions) { + Objects.requireNonNull(createResponseRequest, "createResponseRequest cannot be null"); + + ResponseCreateParams params = ResponseCreateParams.builder() + .additionalBodyProperties(OpenAIJsonHelper.jsonBodyToValueMap(createResponseRequest)) + .build(); + return Mono.fromFuture(this.responseServiceAsync.withRawResponse() + .create(params, requestOptions == null ? RequestOptions.none() : requestOptions)); + } + + /** + * Creates a streaming response from a raw JSON request body and returns the raw HTTP response. + * + *

Delegates to the OpenAI Java SDK's + * {@link ResponseServiceAsync.WithRawResponse#createStreaming(ResponseCreateParams, RequestOptions)}. + * The {@code createResponseRequest} payload is forwarded verbatim as the request body.

+ * + *

The returned {@link HttpResponseFor} wraps a {@link StreamResponse} of + * {@link ResponseStreamEvent} items, which the caller iterates via {@link HttpResponseFor#parse()}. + * Note that the underlying stream produced by the openai-java SDK's raw async streaming API is + * iterator-based and blocking; for a Reactor-friendly streaming surface use + * {@link #createStreamingAzureResponse(AzureCreateResponseOptions, ResponseCreateParams.Builder)} + * instead.

+ * + * @param createResponseRequest the JSON body representing the create-response request; must be a JSON object. + * @param requestOptions optional OpenAI request options; pass {@code null} to use the defaults. + * @return a {@link Mono} emitting the raw HTTP response, parseable as a {@link StreamResponse} of {@link ResponseStreamEvent}. + */ + @ServiceMethod(returns = ReturnType.COLLECTION) + public Mono>> + createResponseStreamWithResponse(BinaryData createResponseRequest, RequestOptions requestOptions) { + Objects.requireNonNull(createResponseRequest, "createResponseRequest cannot be null"); + + ResponseCreateParams params = ResponseCreateParams.builder() + .additionalBodyProperties(OpenAIJsonHelper.jsonBodyToValueMap(createResponseRequest)) + .build(); + return Mono.fromFuture(this.responseServiceAsync.withRawResponse() + .createStreaming(params, requestOptions == null ? RequestOptions.none() : requestOptions)); + } + + /** + * Retrieves a previously created response by id and returns the raw HTTP response. + * + *

Delegates to the OpenAI Java SDK's + * {@link ResponseServiceAsync.WithRawResponse#retrieve(String, RequestOptions)}.

+ * + * @param responseId the id of the response to retrieve. + * @param requestOptions optional OpenAI request options; pass {@code null} to use the defaults. + * @return a {@link Mono} emitting the raw HTTP response, parseable as a {@link Response}. + */ + @ServiceMethod(returns = ReturnType.SINGLE) + public Mono> getResponseWithResponse(String responseId, RequestOptions requestOptions) { + Objects.requireNonNull(responseId, "responseId cannot be null"); + + return Mono.fromFuture(this.responseServiceAsync.withRawResponse() + .retrieve(responseId, requestOptions == null ? RequestOptions.none() : requestOptions)); + } + + /** + * Deletes a previously created response and returns the raw HTTP response. + * + *

Delegates to the OpenAI Java SDK's + * {@link ResponseServiceAsync.WithRawResponse#delete(String, RequestOptions)}.

+ * + *

The returned {@link HttpResponse} exposes the status code and headers; the body (if any) + * can be read via {@link HttpResponse#body()}. Callers must close the response to release the + * underlying connection.

+ * + * @param responseId the id of the response to delete. + * @param requestOptions optional OpenAI request options; pass {@code null} to use the defaults. + * @return a {@link Mono} emitting the raw HTTP response. + */ + @ServiceMethod(returns = ReturnType.SINGLE) + public Mono deleteResponseWithResponse(String responseId, RequestOptions requestOptions) { + Objects.requireNonNull(responseId, "responseId cannot be null"); + + return Mono.fromFuture(this.responseServiceAsync.withRawResponse() + .delete(responseId, requestOptions == null ? RequestOptions.none() : requestOptions)); + } + + /** + * Cancels a previously created response and returns the raw HTTP response. + * + *

Delegates to the OpenAI Java SDK's + * {@link ResponseServiceAsync.WithRawResponse#cancel(String, RequestOptions)}.

+ * + * @param responseId the id of the response to cancel. + * @param requestOptions optional OpenAI request options; pass {@code null} to use the defaults. + * @return a {@link Mono} emitting the raw HTTP response, parseable as a {@link Response}. + */ + @ServiceMethod(returns = ReturnType.SINGLE) + public Mono> cancelResponseWithResponse(String responseId, + RequestOptions requestOptions) { + Objects.requireNonNull(responseId, "responseId cannot be null"); + + return Mono.fromFuture(this.responseServiceAsync.withRawResponse() + .cancel(responseId, requestOptions == null ? RequestOptions.none() : requestOptions)); + } } diff --git a/sdk/ai/azure-ai-agents/src/main/java/com/azure/ai/agents/ResponsesClient.java b/sdk/ai/azure-ai-agents/src/main/java/com/azure/ai/agents/ResponsesClient.java index 687bf8c5316c..96b695109d3e 100644 --- a/sdk/ai/azure-ai-agents/src/main/java/com/azure/ai/agents/ResponsesClient.java +++ b/sdk/ai/azure-ai-agents/src/main/java/com/azure/ai/agents/ResponsesClient.java @@ -11,9 +11,14 @@ import com.azure.core.annotation.ServiceClient; import com.azure.core.annotation.ServiceMethod; import com.azure.core.annotation.ReturnType; +import com.azure.core.util.BinaryData; import com.azure.core.util.IterableStream; import com.openai.client.OpenAIClient; import com.openai.core.JsonValue; +import com.openai.core.RequestOptions; +import com.openai.core.http.HttpResponse; +import com.openai.core.http.HttpResponseFor; +import com.openai.core.http.StreamResponse; import com.openai.models.responses.Response; import com.openai.models.responses.ResponseCreateParams; import com.openai.models.responses.ResponseStreamEvent; @@ -97,4 +102,126 @@ public static AzureCreateResponseDetails getAzureFields(Response response) { AzureCreateResponseDetails::fromJson); } + /** + * Creates a response from a raw JSON request body and returns the raw HTTP response. + * + *

This protocol method delegates to the OpenAI Java SDK's + * {@link ResponseService.WithRawResponse#create(ResponseCreateParams, RequestOptions)}. The + * {@code createResponseRequest} payload is forwarded verbatim as the request body, so callers can + * include Azure-specific extensions (such as + * {@link com.azure.ai.agents.models.AgentReference}) without going through the strongly-typed + * {@link ResponseCreateParams.Builder}.

+ * + *

The returned {@link HttpResponseFor} exposes the status code, headers, and the raw + * response stream via {@code body()}, or the typed {@link Response} via {@code parse()}. Only + * one of {@code body()} or {@code parse()} may be invoked per response, and the caller must + * close the response (e.g. via try-with-resources) to release the underlying connection.

+ * + *

Note: the second parameter is the openai-java {@link RequestOptions} (not the azure-core + * type) so that the OpenAI-supported options (timeout, response validation) translate + * faithfully. Additional headers or query parameters must be supplied via the OpenAI request + * builder pattern (e.g. by using {@link #createAzureResponse} for fully-typed requests).

+ * + * @param createResponseRequest the JSON body representing the create-response request; must be a JSON object. + * @param requestOptions optional OpenAI request options; pass {@code null} to use the defaults. + * @return the raw HTTP response, parseable as a {@link Response}. + */ + @ServiceMethod(returns = ReturnType.SINGLE) + public HttpResponseFor createResponseWithResponse(BinaryData createResponseRequest, + RequestOptions requestOptions) { + Objects.requireNonNull(createResponseRequest, "createResponseRequest cannot be null"); + + ResponseCreateParams params = ResponseCreateParams.builder() + .additionalBodyProperties(OpenAIJsonHelper.jsonBodyToValueMap(createResponseRequest)) + .build(); + return this.responseService.withRawResponse() + .create(params, requestOptions == null ? RequestOptions.none() : requestOptions); + } + + /** + * Creates a streaming response from a raw JSON request body and returns the raw HTTP response. + * + *

Delegates to the OpenAI Java SDK's + * {@link ResponseService.WithRawResponse#createStreaming(ResponseCreateParams, RequestOptions)}. + * The {@code createResponseRequest} payload is forwarded verbatim as the request body.

+ * + *

The returned {@link HttpResponseFor} wraps a {@link StreamResponse} of + * {@link ResponseStreamEvent} items, which the caller iterates via {@link HttpResponseFor#parse()}. + * The underlying stream must be closed when iteration completes; the typical pattern is + * try-with-resources on either the {@link HttpResponseFor} or the parsed {@link StreamResponse}.

+ * + * @param createResponseRequest the JSON body representing the create-response request; must be a JSON object. + * @param requestOptions optional OpenAI request options; pass {@code null} to use the defaults. + * @return the raw HTTP response, parseable as a {@link StreamResponse} of {@link ResponseStreamEvent}. + */ + @ServiceMethod(returns = ReturnType.COLLECTION) + public HttpResponseFor> + createResponseStreamWithResponse(BinaryData createResponseRequest, RequestOptions requestOptions) { + Objects.requireNonNull(createResponseRequest, "createResponseRequest cannot be null"); + + ResponseCreateParams params = ResponseCreateParams.builder() + .additionalBodyProperties(OpenAIJsonHelper.jsonBodyToValueMap(createResponseRequest)) + .build(); + return this.responseService.withRawResponse() + .createStreaming(params, requestOptions == null ? RequestOptions.none() : requestOptions); + } + + /** + * Retrieves a previously created response by id and returns the raw HTTP response. + * + *

Delegates to the OpenAI Java SDK's + * {@link ResponseService.WithRawResponse#retrieve(String, RequestOptions)}.

+ * + * @param responseId the id of the response to retrieve. + * @param requestOptions optional OpenAI request options; pass {@code null} to use the defaults. + * @return the raw HTTP response, parseable as a {@link Response}. + */ + @ServiceMethod(returns = ReturnType.SINGLE) + public HttpResponseFor getResponseWithResponse(String responseId, RequestOptions requestOptions) { + Objects.requireNonNull(responseId, "responseId cannot be null"); + + return this.responseService.withRawResponse() + .retrieve(responseId, requestOptions == null ? RequestOptions.none() : requestOptions); + } + + /** + * Deletes a previously created response and returns the raw HTTP response. + * + *

Delegates to the OpenAI Java SDK's + * {@link ResponseService.WithRawResponse#delete(String, RequestOptions)}.

+ * + *

The returned {@link HttpResponse} exposes the status code and headers; the body (if any) + * can be read via {@link HttpResponse#body()}. Callers must close the response to release the + * underlying connection.

+ * + * @param responseId the id of the response to delete. + * @param requestOptions optional OpenAI request options; pass {@code null} to use the defaults. + * @return the raw HTTP response. + */ + @ServiceMethod(returns = ReturnType.SINGLE) + public HttpResponse deleteResponseWithResponse(String responseId, RequestOptions requestOptions) { + Objects.requireNonNull(responseId, "responseId cannot be null"); + + return this.responseService.withRawResponse() + .delete(responseId, requestOptions == null ? RequestOptions.none() : requestOptions); + } + + /** + * Cancels a previously created response and returns the raw HTTP response. + * + *

Delegates to the OpenAI Java SDK's + * {@link ResponseService.WithRawResponse#cancel(String, RequestOptions)}.

+ * + * @param responseId the id of the response to cancel. + * @param requestOptions optional OpenAI request options; pass {@code null} to use the defaults. + * @return the raw HTTP response, parseable as a {@link Response}. + */ + @ServiceMethod(returns = ReturnType.SINGLE) + public HttpResponseFor cancelResponseWithResponse(String responseId, RequestOptions requestOptions) { + Objects.requireNonNull(responseId, "responseId cannot be null"); + + return this.responseService.withRawResponse() + .cancel(responseId, requestOptions == null ? RequestOptions.none() : requestOptions); + } + } diff --git a/sdk/ai/azure-ai-agents/src/main/java/com/azure/ai/agents/implementation/OpenAIJsonHelper.java b/sdk/ai/azure-ai-agents/src/main/java/com/azure/ai/agents/implementation/OpenAIJsonHelper.java index d5fdcab14f1f..c7d15a77fcf2 100644 --- a/sdk/ai/azure-ai-agents/src/main/java/com/azure/ai/agents/implementation/OpenAIJsonHelper.java +++ b/sdk/ai/azure-ai-agents/src/main/java/com/azure/ai/agents/implementation/OpenAIJsonHelper.java @@ -160,18 +160,43 @@ public static > Map toJsonValue } try { String json = BinaryData.fromObject(obj).toString(); - Map map = MAPPER.readValue(json, new TypeReference>() { - }); - Map result = new HashMap<>(); - for (Map.Entry entry : map.entrySet()) { - result.put(entry.getKey(), JsonValue.from(entry.getValue())); - } - return result; + return jsonStringToJsonValueMap(json); } catch (IOException e) { throw new RuntimeException("Failed to flatten JsonSerializable to JsonValue map", e); } } + /** + * Parses raw JSON bytes representing a top-level JSON object into a map of property names to + * {@link JsonValue} entries, suitable for placing on an openai-java request builder via + * {@code additionalBodyProperties(Map)}. The strongly-typed builder fields remain unset, so + * the serialized output of the resulting params matches the original JSON. + * + * @param body the JSON body bytes; the content must represent a JSON object. + * @return a map of property names to {@link JsonValue} entries, or an empty map if the input is null. + * @throws RuntimeException if the input is not a valid JSON object. + */ + public static Map jsonBodyToValueMap(BinaryData body) { + if (body == null) { + return new HashMap<>(); + } + try { + return jsonStringToJsonValueMap(body.toString()); + } catch (IOException e) { + throw new RuntimeException("Failed to parse JSON body to JsonValue map", e); + } + } + + private static Map jsonStringToJsonValueMap(String json) throws IOException { + Map map = MAPPER.readValue(json, new TypeReference>() { + }); + Map result = new HashMap<>(); + for (Map.Entry entry : map.entrySet()) { + result.put(entry.getKey(), JsonValue.from(entry.getValue())); + } + return result; + } + /** * Deserializes a map of {@link JsonValue} entries (typically from * {@code response._additionalProperties()}) into an Azure SDK type that implements From 2ef055f392c9bddcbc134196cd16699006aca78b Mon Sep 17 00:00:00 2001 From: Jose Alvarez Date: Fri, 22 May 2026 11:47:34 +0200 Subject: [PATCH 2/6] Add protocol-method tests for ResponsesClient and ResponsesAsyncClient Mirrors the existing basicCRUDOperations tests against the new createResponseWithResponse protocol methods (BinaryData + RequestOptions), parses the returned HttpResponseFor, and asserts on the same shape (non-null Response with an id) as the typed-method tests. Tests remain @Disabled pending public preview recordings, consistent with the existing typed tests. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../azure/ai/agents/ResponsesAsyncTests.java | 20 +++++++++ .../com/azure/ai/agents/ResponsesTests.java | 44 +++++++++++++++++++ 2 files changed, 64 insertions(+) diff --git a/sdk/ai/azure-ai-agents/src/test/java/com/azure/ai/agents/ResponsesAsyncTests.java b/sdk/ai/azure-ai-agents/src/test/java/com/azure/ai/agents/ResponsesAsyncTests.java index 2d12d824cc57..c63647a76a37 100644 --- a/sdk/ai/azure-ai-agents/src/test/java/com/azure/ai/agents/ResponsesAsyncTests.java +++ b/sdk/ai/azure-ai-agents/src/test/java/com/azure/ai/agents/ResponsesAsyncTests.java @@ -4,6 +4,8 @@ package com.azure.ai.agents; import com.azure.core.http.HttpClient; +import com.azure.core.util.BinaryData; +import com.openai.core.http.HttpResponseFor; import com.openai.models.responses.Response; import com.openai.models.responses.ResponseCreateParams; import org.junit.jupiter.api.Disabled; @@ -13,10 +15,14 @@ import java.util.concurrent.ExecutionException; import static com.azure.ai.agents.TestUtils.DISPLAY_NAME_WITH_ARGUMENTS; +import static org.junit.jupiter.api.Assertions.assertNotNull; @Disabled("Disabled for lack of recordings. Needs to be enabled on the Public Preview release.") public class ResponsesAsyncTests extends ClientTestBase { + private static final String CREATE_RESPONSE_BODY + = "{\"input\":\"Hello, how can you help me?\",\"model\":\"gpt-4o\"}"; + @ParameterizedTest(name = DISPLAY_NAME_WITH_ARGUMENTS) @MethodSource("com.azure.ai.agents.TestUtils#getTestParameters") public void basicCRUDOperations(HttpClient httpClient, AgentsServiceVersion serviceVersion) @@ -29,4 +35,18 @@ public void basicCRUDOperations(HttpClient httpClient, AgentsServiceVersion serv Response response = client.getResponseServiceAsync().create(responsesRequest).get(); System.out.println(response); } + + @ParameterizedTest(name = DISPLAY_NAME_WITH_ARGUMENTS) + @MethodSource("com.azure.ai.agents.TestUtils#getTestParameters") + public void basicCRUDOperationsWithResponse(HttpClient httpClient, AgentsServiceVersion serviceVersion) { + ResponsesAsyncClient client = getResponsesAsyncClient(httpClient, serviceVersion); + + HttpResponseFor rawResponse + = client.createResponseWithResponse(BinaryData.fromString(CREATE_RESPONSE_BODY), null).block(); + + assertNotNull(rawResponse); + Response response = rawResponse.parse(); + assertNotNull(response); + System.out.println(response); + } } diff --git a/sdk/ai/azure-ai-agents/src/test/java/com/azure/ai/agents/ResponsesTests.java b/sdk/ai/azure-ai-agents/src/test/java/com/azure/ai/agents/ResponsesTests.java index 49459f16b2fb..2f23e741c227 100644 --- a/sdk/ai/azure-ai-agents/src/test/java/com/azure/ai/agents/ResponsesTests.java +++ b/sdk/ai/azure-ai-agents/src/test/java/com/azure/ai/agents/ResponsesTests.java @@ -4,6 +4,8 @@ package com.azure.ai.agents; import com.azure.core.http.HttpClient; +import com.azure.core.util.BinaryData; +import com.openai.core.http.HttpResponseFor; import com.openai.models.responses.Response; import com.openai.models.responses.ResponseCreateParams; import org.junit.jupiter.api.Disabled; @@ -16,6 +18,9 @@ @Disabled("Disabled for lack of recordings. Needs to be enabled on the Public Preview release.") public class ResponsesTests extends ClientTestBase { + private static final String CREATE_RESPONSE_BODY + = "{\"input\":\"Hello, how can you help me?\",\"model\":\"gpt-4o\"}"; + @ParameterizedTest(name = DISPLAY_NAME_WITH_ARGUMENTS) @MethodSource("com.azure.ai.agents.TestUtils#getTestParameters") public void basicCRUDOperations(HttpClient httpClient, AgentsServiceVersion serviceVersion) @@ -59,4 +64,43 @@ public void basicCRUDOperations(HttpClient httpClient, AgentsServiceVersion serv // Deletion - currently returning 500 // client.getOpenAIClient().delete(createdResponse.id()); } + + @ParameterizedTest(name = DISPLAY_NAME_WITH_ARGUMENTS) + @MethodSource("com.azure.ai.agents.TestUtils#getTestParameters") + public void basicCRUDOperationsWithResponse(HttpClient httpClient, AgentsServiceVersion serviceVersion) + throws InterruptedException { + ResponsesClient client = getResponsesSyncClient(httpClient, serviceVersion); + + // Creation + HttpResponseFor rawResponse + = client.createResponseWithResponse(BinaryData.fromString(CREATE_RESPONSE_BODY), null); + + assertNotNull(rawResponse); + Response createdResponse = rawResponse.parse(); + assertNotNull(createdResponse); + assertNotNull(createdResponse.id()); + + // Retrieval - currently returning 500 + // HttpResponseFor retrievedRaw = client.getResponseWithResponse(createdResponse.id(), null); + // assertNotNull(retrievedRaw); + // Response retrievedResponse = retrievedRaw.parse(); + // assertNotNull(retrievedResponse); + // assertNotNull(retrievedResponse.id()); + + // Cancel - will have to look into the async tests for this + // HttpResponseFor cancelableRaw = client.createResponseWithResponse( + // BinaryData.fromString("{\"previous_response_id\":\"" + createdResponse.previousResponseId().orElse(null) + // + "\",\"input\":\"Tell me a long story about a chicken trying to cross the road.\"," + // + "\"reasoning\":{\"effort\":\"high\"},\"background\":true}"), + // null); + // HttpResponseFor cancellationRaw + // = client.cancelResponseWithResponse(cancelableRaw.parse().id(), null); + // assertNotNull(cancellationRaw); + // Response cancellationResponse = cancellationRaw.parse(); + // assertNotNull(cancellationResponse); + // assertNotNull(cancellationResponse.id()); + + // Deletion - currently returning 500 + // client.deleteResponseWithResponse(createdResponse.id(), null); + } } From b92988b10a362e830997026be0a10532a7b2ffa4 Mon Sep 17 00:00:00 2001 From: Jose Alvarez Date: Fri, 22 May 2026 12:55:12 +0200 Subject: [PATCH 3/6] Enable Responses tests with full create/retrieve/delete/cancel/input-items coverage Cleanup, expansion, and enablement of ResponsesTests and ResponsesAsyncTests: 1. Removed the stale '// currently returning 500' comments and the orphan getOpenAIClient() pseudo-code blocks from the initial Agents V2 commit (PR #47134). Live calls against the service confirmed all four previously-flagged endpoints (retrieve, delete, cancel, input_items) now return 200. 2. Expanded both typed and protocol-method tests to actually exercise the full surface: create -> retrieve -> input_items -> delete in basicCRUDOperations, plus a separate cancelBackgroundResponse / cancelBackgroundResponseWithResponse that creates a background:true response and cancels it (asserting CANCELLED or COMPLETED, since the response may finish before cancel hits). 4 test methods per class (sync + async) x 2 surfaces = 8 tests total. 3. Dropped the class-level @Disabled annotations now that recordings exist. Captured live recordings via AZURE_TEST_MODE=RECORD against the Foundry endpoint, pushed via 'test-proxy push -a assets.json' to Azure/azure-sdk-assets, and bumped the assets.json Tag to java/ai/azure-ai-agents_7a1b5ec037. Verified by re-running in default (playback) mode: all 8 tests pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- sdk/ai/azure-ai-agents/CHANGELOG.md | 4 + sdk/ai/azure-ai-agents/assets.json | 2 +- .../azure/ai/agents/ResponsesAsyncTests.java | 105 ++++++++++-- .../com/azure/ai/agents/ResponsesTests.java | 156 ++++++++++-------- 4 files changed, 189 insertions(+), 78 deletions(-) diff --git a/sdk/ai/azure-ai-agents/CHANGELOG.md b/sdk/ai/azure-ai-agents/CHANGELOG.md index eea929205c6a..4278c7bf022b 100644 --- a/sdk/ai/azure-ai-agents/CHANGELOG.md +++ b/sdk/ai/azure-ai-agents/CHANGELOG.md @@ -6,6 +6,10 @@ - Added protocol-style methods on `ResponsesClient` and `ResponsesAsyncClient` that accept a raw JSON request body (`BinaryData`) and a `com.openai.core.RequestOptions`, and return the openai-java raw HTTP response (`HttpResponseFor`, `HttpResponseFor>`, and `HttpResponse`). New methods: `createResponseWithResponse`, `createResponseStreamWithResponse`, `getResponseWithResponse`, `deleteResponseWithResponse`, and `cancelResponseWithResponse`. These delegate to the underlying openai-java `ResponseService.withRawResponse()` surface and continue to flow through the Azure HTTP pipeline. +### Other Changes + +- Enabled `ResponsesTests` and `ResponsesAsyncTests` (previously `@Disabled`) with full create/retrieve/delete/input-items and background-cancel coverage for both the typed (`ResponseService` / `ResponseServiceAsync`) and new protocol-method surfaces. Recordings published to `Azure/azure-sdk-assets` and referenced from `assets.json`. + ### Breaking Changes ### Bugs Fixed diff --git a/sdk/ai/azure-ai-agents/assets.json b/sdk/ai/azure-ai-agents/assets.json index 87e9dbe90acc..078cc053330f 100644 --- a/sdk/ai/azure-ai-agents/assets.json +++ b/sdk/ai/azure-ai-agents/assets.json @@ -2,5 +2,5 @@ "AssetsRepo": "Azure/azure-sdk-assets", "AssetsRepoPrefixPath": "java", "TagPrefix": "java/ai/azure-ai-agents", - "Tag": "java/ai/azure-ai-agents_69308f6d56" + "Tag": "java/ai/azure-ai-agents_7a1b5ec037" } diff --git a/sdk/ai/azure-ai-agents/src/test/java/com/azure/ai/agents/ResponsesAsyncTests.java b/sdk/ai/azure-ai-agents/src/test/java/com/azure/ai/agents/ResponsesAsyncTests.java index c63647a76a37..6ec0fd5db280 100644 --- a/sdk/ai/azure-ai-agents/src/test/java/com/azure/ai/agents/ResponsesAsyncTests.java +++ b/sdk/ai/azure-ai-agents/src/test/java/com/azure/ai/agents/ResponsesAsyncTests.java @@ -5,35 +5,83 @@ import com.azure.core.http.HttpClient; import com.azure.core.util.BinaryData; +import com.openai.core.http.HttpResponse; import com.openai.core.http.HttpResponseFor; import com.openai.models.responses.Response; import com.openai.models.responses.ResponseCreateParams; -import org.junit.jupiter.api.Disabled; +import com.openai.models.responses.ResponseStatus; +import com.openai.models.responses.inputitems.InputItemListPageAsync; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; import java.util.concurrent.ExecutionException; import static com.azure.ai.agents.TestUtils.DISPLAY_NAME_WITH_ARGUMENTS; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; -@Disabled("Disabled for lack of recordings. Needs to be enabled on the Public Preview release.") public class ResponsesAsyncTests extends ClientTestBase { private static final String CREATE_RESPONSE_BODY = "{\"input\":\"Hello, how can you help me?\",\"model\":\"gpt-4o\"}"; + private static final String CREATE_BACKGROUND_RESPONSE_BODY + = "{\"input\":\"Tell me a very long story about a chicken trying to cross the road.\"," + + "\"model\":\"gpt-4o\",\"background\":true}"; + @ParameterizedTest(name = DISPLAY_NAME_WITH_ARGUMENTS) @MethodSource("com.azure.ai.agents.TestUtils#getTestParameters") public void basicCRUDOperations(HttpClient httpClient, AgentsServiceVersion serviceVersion) throws ExecutionException, InterruptedException { ResponsesAsyncClient client = getResponsesAsyncClient(httpClient, serviceVersion); - ResponseCreateParams responsesRequest - = new ResponseCreateParams.Builder().input("Hello, how can you help me?").model("gpt-4o").build(); + // create + ResponseCreateParams createRequest + = ResponseCreateParams.builder().input("Hello, how can you help me?").model("gpt-4o").build(); + Response createdResponse = client.getResponseServiceAsync().create(createRequest).get(); + assertNotNull(createdResponse); + assertNotNull(createdResponse.id()); + + // retrieve + Response retrievedResponse = client.getResponseServiceAsync().retrieve(createdResponse.id()).get(); + assertNotNull(retrievedResponse); + assertEquals(createdResponse.id(), retrievedResponse.id()); + + // input items + InputItemListPageAsync inputItems + = client.getResponseServiceAsync().inputItems().list(createdResponse.id()).get(); + assertNotNull(inputItems); + assertNotNull(inputItems.data()); + assertFalse(inputItems.data().isEmpty()); - Response response = client.getResponseServiceAsync().create(responsesRequest).get(); - System.out.println(response); + // delete + client.getResponseServiceAsync().delete(createdResponse.id()).get(); + } + + @ParameterizedTest(name = DISPLAY_NAME_WITH_ARGUMENTS) + @MethodSource("com.azure.ai.agents.TestUtils#getTestParameters") + public void cancelBackgroundResponse(HttpClient httpClient, AgentsServiceVersion serviceVersion) + throws ExecutionException, InterruptedException { + ResponsesAsyncClient client = getResponsesAsyncClient(httpClient, serviceVersion); + + ResponseCreateParams createRequest = ResponseCreateParams.builder() + .input("Tell me a very long story about a chicken trying to cross the road.") + .model("gpt-4o") + .background(true) + .build(); + Response backgroundResponse = client.getResponseServiceAsync().create(createRequest).get(); + assertNotNull(backgroundResponse); + assertNotNull(backgroundResponse.id()); + + Response cancelledResponse = client.getResponseServiceAsync().cancel(backgroundResponse.id()).get(); + assertNotNull(cancelledResponse); + assertEquals(backgroundResponse.id(), cancelledResponse.id()); + assertTrue(cancelledResponse.status().isPresent()); + ResponseStatus status = cancelledResponse.status().get(); + assertTrue(status.equals(ResponseStatus.CANCELLED) || status.equals(ResponseStatus.COMPLETED), + "Expected CANCELLED or COMPLETED status but was " + status); } @ParameterizedTest(name = DISPLAY_NAME_WITH_ARGUMENTS) @@ -41,12 +89,47 @@ public void basicCRUDOperations(HttpClient httpClient, AgentsServiceVersion serv public void basicCRUDOperationsWithResponse(HttpClient httpClient, AgentsServiceVersion serviceVersion) { ResponsesAsyncClient client = getResponsesAsyncClient(httpClient, serviceVersion); - HttpResponseFor rawResponse + // create + HttpResponseFor createdRaw = client.createResponseWithResponse(BinaryData.fromString(CREATE_RESPONSE_BODY), null).block(); + assertNotNull(createdRaw); + Response createdResponse = createdRaw.parse(); + assertNotNull(createdResponse); + assertNotNull(createdResponse.id()); + + // retrieve + HttpResponseFor retrievedRaw = client.getResponseWithResponse(createdResponse.id(), null).block(); + assertNotNull(retrievedRaw); + Response retrievedResponse = retrievedRaw.parse(); + assertNotNull(retrievedResponse); + assertEquals(createdResponse.id(), retrievedResponse.id()); + + // delete + HttpResponse deletedRaw = client.deleteResponseWithResponse(createdResponse.id(), null).block(); + assertNotNull(deletedRaw); + } + + @ParameterizedTest(name = DISPLAY_NAME_WITH_ARGUMENTS) + @MethodSource("com.azure.ai.agents.TestUtils#getTestParameters") + public void cancelBackgroundResponseWithResponse(HttpClient httpClient, AgentsServiceVersion serviceVersion) { + ResponsesAsyncClient client = getResponsesAsyncClient(httpClient, serviceVersion); + + HttpResponseFor backgroundRaw + = client.createResponseWithResponse(BinaryData.fromString(CREATE_BACKGROUND_RESPONSE_BODY), null).block(); + assertNotNull(backgroundRaw); + Response backgroundResponse = backgroundRaw.parse(); + assertNotNull(backgroundResponse); + assertNotNull(backgroundResponse.id()); - assertNotNull(rawResponse); - Response response = rawResponse.parse(); - assertNotNull(response); - System.out.println(response); + HttpResponseFor cancelledRaw + = client.cancelResponseWithResponse(backgroundResponse.id(), null).block(); + assertNotNull(cancelledRaw); + Response cancelledResponse = cancelledRaw.parse(); + assertNotNull(cancelledResponse); + assertEquals(backgroundResponse.id(), cancelledResponse.id()); + assertTrue(cancelledResponse.status().isPresent()); + ResponseStatus status = cancelledResponse.status().get(); + assertTrue(status.equals(ResponseStatus.CANCELLED) || status.equals(ResponseStatus.COMPLETED), + "Expected CANCELLED or COMPLETED status but was " + status); } } diff --git a/sdk/ai/azure-ai-agents/src/test/java/com/azure/ai/agents/ResponsesTests.java b/sdk/ai/azure-ai-agents/src/test/java/com/azure/ai/agents/ResponsesTests.java index 2f23e741c227..319b3aad5422 100644 --- a/sdk/ai/azure-ai-agents/src/test/java/com/azure/ai/agents/ResponsesTests.java +++ b/sdk/ai/azure-ai-agents/src/test/java/com/azure/ai/agents/ResponsesTests.java @@ -5,102 +5,126 @@ import com.azure.core.http.HttpClient; import com.azure.core.util.BinaryData; +import com.openai.core.http.HttpResponse; import com.openai.core.http.HttpResponseFor; import com.openai.models.responses.Response; import com.openai.models.responses.ResponseCreateParams; -import org.junit.jupiter.api.Disabled; +import com.openai.models.responses.ResponseStatus; +import com.openai.models.responses.inputitems.InputItemListPage; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; import static com.azure.ai.agents.TestUtils.DISPLAY_NAME_WITH_ARGUMENTS; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; -@Disabled("Disabled for lack of recordings. Needs to be enabled on the Public Preview release.") public class ResponsesTests extends ClientTestBase { private static final String CREATE_RESPONSE_BODY = "{\"input\":\"Hello, how can you help me?\",\"model\":\"gpt-4o\"}"; + private static final String CREATE_BACKGROUND_RESPONSE_BODY + = "{\"input\":\"Tell me a very long story about a chicken trying to cross the road.\"," + + "\"model\":\"gpt-4o\",\"background\":true}"; + @ParameterizedTest(name = DISPLAY_NAME_WITH_ARGUMENTS) @MethodSource("com.azure.ai.agents.TestUtils#getTestParameters") - public void basicCRUDOperations(HttpClient httpClient, AgentsServiceVersion serviceVersion) - throws InterruptedException { + public void basicCRUDOperations(HttpClient httpClient, AgentsServiceVersion serviceVersion) { ResponsesClient client = getResponsesSyncClient(httpClient, serviceVersion); - ResponseCreateParams responsesRequest - = new ResponseCreateParams.Builder().input("Hello, how can you help me?").model("gpt-4o").build(); - - // Creation - Response createdResponse = client.getResponseService().create(responsesRequest); - + // create + ResponseCreateParams createRequest + = ResponseCreateParams.builder().input("Hello, how can you help me?").model("gpt-4o").build(); + Response createdResponse = client.getResponseService().create(createRequest); assertNotNull(createdResponse); assertNotNull(createdResponse.id()); - // Retrieval - currently returning 500 - // Response retrievedResponse = client.getOpenAIClient().retrieve(createdResponse.id()); - // - // assertNotNull(retrievedResponse); - // assertNotNull(retrievedResponse.id()); - - // Cancel - will have to look into the async tests for this - // Response cancelableResponse = client.getOpenAIClient().create( - // ResponseCreateParams.builder().previousResponseId(createdResponse.previousResponseId()) - // .input("Tell me a long story about a chicken trying to cross the road.") - // .reasoning(Reasoning.builder().effort(ReasoningEffort.HIGH).build()) - // .background(true) - // .build()); - // Response cancelationResponse = client.getOpenAIClient().cancel(cancelableResponse.id()); - // - // assertNotNull(cancelationResponse); - // assertNotNull(cancelationResponse.id()); - - // Input items - currently returning 500 - // InputItemListPage itemList = client.getOpenAIClient().inputItems().list(createdResponse.id()); - // - // assertNotNull(itemList); - // assertNotNull(itemList.data()); - // assertFalse(itemList.data().isEmpty()); - - // Deletion - currently returning 500 - // client.getOpenAIClient().delete(createdResponse.id()); + // retrieve + Response retrievedResponse = client.getResponseService().retrieve(createdResponse.id()); + assertNotNull(retrievedResponse); + assertEquals(createdResponse.id(), retrievedResponse.id()); + + // input items + InputItemListPage inputItems = client.getResponseService().inputItems().list(createdResponse.id()); + assertNotNull(inputItems); + assertNotNull(inputItems.data()); + assertFalse(inputItems.data().isEmpty()); + + // delete + client.getResponseService().delete(createdResponse.id()); } @ParameterizedTest(name = DISPLAY_NAME_WITH_ARGUMENTS) @MethodSource("com.azure.ai.agents.TestUtils#getTestParameters") - public void basicCRUDOperationsWithResponse(HttpClient httpClient, AgentsServiceVersion serviceVersion) - throws InterruptedException { + public void cancelBackgroundResponse(HttpClient httpClient, AgentsServiceVersion serviceVersion) { ResponsesClient client = getResponsesSyncClient(httpClient, serviceVersion); - // Creation - HttpResponseFor rawResponse - = client.createResponseWithResponse(BinaryData.fromString(CREATE_RESPONSE_BODY), null); + ResponseCreateParams createRequest = ResponseCreateParams.builder() + .input("Tell me a very long story about a chicken trying to cross the road.") + .model("gpt-4o") + .background(true) + .build(); + Response backgroundResponse = client.getResponseService().create(createRequest); + assertNotNull(backgroundResponse); + assertNotNull(backgroundResponse.id()); + + Response cancelledResponse = client.getResponseService().cancel(backgroundResponse.id()); + assertNotNull(cancelledResponse); + assertEquals(backgroundResponse.id(), cancelledResponse.id()); + assertTrue(cancelledResponse.status().isPresent()); + ResponseStatus status = cancelledResponse.status().get(); + // Background responses may finish between create and cancel; accept either terminal state. + assertTrue(status.equals(ResponseStatus.CANCELLED) || status.equals(ResponseStatus.COMPLETED), + "Expected CANCELLED or COMPLETED status but was " + status); + } - assertNotNull(rawResponse); - Response createdResponse = rawResponse.parse(); + @ParameterizedTest(name = DISPLAY_NAME_WITH_ARGUMENTS) + @MethodSource("com.azure.ai.agents.TestUtils#getTestParameters") + public void basicCRUDOperationsWithResponse(HttpClient httpClient, AgentsServiceVersion serviceVersion) { + ResponsesClient client = getResponsesSyncClient(httpClient, serviceVersion); + + // create + HttpResponseFor createdRaw + = client.createResponseWithResponse(BinaryData.fromString(CREATE_RESPONSE_BODY), null); + assertNotNull(createdRaw); + Response createdResponse = createdRaw.parse(); assertNotNull(createdResponse); assertNotNull(createdResponse.id()); - // Retrieval - currently returning 500 - // HttpResponseFor retrievedRaw = client.getResponseWithResponse(createdResponse.id(), null); - // assertNotNull(retrievedRaw); - // Response retrievedResponse = retrievedRaw.parse(); - // assertNotNull(retrievedResponse); - // assertNotNull(retrievedResponse.id()); - - // Cancel - will have to look into the async tests for this - // HttpResponseFor cancelableRaw = client.createResponseWithResponse( - // BinaryData.fromString("{\"previous_response_id\":\"" + createdResponse.previousResponseId().orElse(null) - // + "\",\"input\":\"Tell me a long story about a chicken trying to cross the road.\"," - // + "\"reasoning\":{\"effort\":\"high\"},\"background\":true}"), - // null); - // HttpResponseFor cancellationRaw - // = client.cancelResponseWithResponse(cancelableRaw.parse().id(), null); - // assertNotNull(cancellationRaw); - // Response cancellationResponse = cancellationRaw.parse(); - // assertNotNull(cancellationResponse); - // assertNotNull(cancellationResponse.id()); - - // Deletion - currently returning 500 - // client.deleteResponseWithResponse(createdResponse.id(), null); + // retrieve + HttpResponseFor retrievedRaw = client.getResponseWithResponse(createdResponse.id(), null); + assertNotNull(retrievedRaw); + Response retrievedResponse = retrievedRaw.parse(); + assertNotNull(retrievedResponse); + assertEquals(createdResponse.id(), retrievedResponse.id()); + + // delete + HttpResponse deletedRaw = client.deleteResponseWithResponse(createdResponse.id(), null); + assertNotNull(deletedRaw); + } + + @ParameterizedTest(name = DISPLAY_NAME_WITH_ARGUMENTS) + @MethodSource("com.azure.ai.agents.TestUtils#getTestParameters") + public void cancelBackgroundResponseWithResponse(HttpClient httpClient, AgentsServiceVersion serviceVersion) { + ResponsesClient client = getResponsesSyncClient(httpClient, serviceVersion); + + HttpResponseFor backgroundRaw + = client.createResponseWithResponse(BinaryData.fromString(CREATE_BACKGROUND_RESPONSE_BODY), null); + assertNotNull(backgroundRaw); + Response backgroundResponse = backgroundRaw.parse(); + assertNotNull(backgroundResponse); + assertNotNull(backgroundResponse.id()); + + HttpResponseFor cancelledRaw = client.cancelResponseWithResponse(backgroundResponse.id(), null); + assertNotNull(cancelledRaw); + Response cancelledResponse = cancelledRaw.parse(); + assertNotNull(cancelledResponse); + assertEquals(backgroundResponse.id(), cancelledResponse.id()); + assertTrue(cancelledResponse.status().isPresent()); + ResponseStatus status = cancelledResponse.status().get(); + assertTrue(status.equals(ResponseStatus.CANCELLED) || status.equals(ResponseStatus.COMPLETED), + "Expected CANCELLED or COMPLETED status but was " + status); } } From 1097241bc530117d9b2f6aaea13621fab54d1877 Mon Sep 17 00:00:00 2001 From: Jose Alvarez Date: Fri, 22 May 2026 13:19:20 +0200 Subject: [PATCH 4/6] Narrow Responses protocol-methods scope to create + createStream only Drop the getResponseWithResponse, deleteResponseWithResponse, and cancelResponseWithResponse protocol methods added previously. The ResponsesClient/ResponsesAsyncClient surface now exposes protocol-method variants only for createAzureResponse and createStreamingAzureResponse, matching the original typed surface. Tests: - Trim basicCRUDOperationsWithResponse to exercise createResponseWithResponse only. - Add basicStreamingOperationsWithResponse to cover createResponseStreamWithResponse. - Drop cancelBackgroundResponseWithResponse. - Re-recorded affected sessions; bumped assets.json tag. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- sdk/ai/azure-ai-agents/CHANGELOG.md | 4 +- sdk/ai/azure-ai-agents/assets.json | 2 +- .../azure/ai/agents/ResponsesAsyncClient.java | 60 ------------------- .../com/azure/ai/agents/ResponsesClient.java | 59 ------------------ .../azure/ai/agents/ResponsesAsyncTests.java | 45 +++++--------- .../com/azure/ai/agents/ResponsesTests.java | 45 +++++--------- 6 files changed, 32 insertions(+), 183 deletions(-) diff --git a/sdk/ai/azure-ai-agents/CHANGELOG.md b/sdk/ai/azure-ai-agents/CHANGELOG.md index 4278c7bf022b..527ff8a8e270 100644 --- a/sdk/ai/azure-ai-agents/CHANGELOG.md +++ b/sdk/ai/azure-ai-agents/CHANGELOG.md @@ -4,11 +4,11 @@ ### Features Added -- Added protocol-style methods on `ResponsesClient` and `ResponsesAsyncClient` that accept a raw JSON request body (`BinaryData`) and a `com.openai.core.RequestOptions`, and return the openai-java raw HTTP response (`HttpResponseFor`, `HttpResponseFor>`, and `HttpResponse`). New methods: `createResponseWithResponse`, `createResponseStreamWithResponse`, `getResponseWithResponse`, `deleteResponseWithResponse`, and `cancelResponseWithResponse`. These delegate to the underlying openai-java `ResponseService.withRawResponse()` surface and continue to flow through the Azure HTTP pipeline. +- Added protocol-style methods on `ResponsesClient` and `ResponsesAsyncClient` that accept a raw JSON request body (`BinaryData`) and a `com.openai.core.RequestOptions`, and return the openai-java raw HTTP response. These mirror the existing `createAzureResponse` and `createStreamingAzureResponse` typed surface: `createResponseWithResponse` (returns `HttpResponseFor`) and `createResponseStreamWithResponse` (returns `HttpResponseFor>`). They delegate to the underlying openai-java `ResponseService.withRawResponse()` surface and continue to flow through the Azure HTTP pipeline. ### Other Changes -- Enabled `ResponsesTests` and `ResponsesAsyncTests` (previously `@Disabled`) with full create/retrieve/delete/input-items and background-cancel coverage for both the typed (`ResponseService` / `ResponseServiceAsync`) and new protocol-method surfaces. Recordings published to `Azure/azure-sdk-assets` and referenced from `assets.json`. +- Enabled `ResponsesTests` and `ResponsesAsyncTests` (previously `@Disabled`) with create/retrieve/delete/input-items and background-cancel coverage for the typed (`ResponseService` / `ResponseServiceAsync`) surface, plus coverage for the new protocol-method surface. Recordings published to `Azure/azure-sdk-assets` and referenced from `assets.json`. ### Breaking Changes diff --git a/sdk/ai/azure-ai-agents/assets.json b/sdk/ai/azure-ai-agents/assets.json index 078cc053330f..67067ff3bcf7 100644 --- a/sdk/ai/azure-ai-agents/assets.json +++ b/sdk/ai/azure-ai-agents/assets.json @@ -2,5 +2,5 @@ "AssetsRepo": "Azure/azure-sdk-assets", "AssetsRepoPrefixPath": "java", "TagPrefix": "java/ai/azure-ai-agents", - "Tag": "java/ai/azure-ai-agents_7a1b5ec037" + "Tag": "java/ai/azure-ai-agents_a3142fe843" } diff --git a/sdk/ai/azure-ai-agents/src/main/java/com/azure/ai/agents/ResponsesAsyncClient.java b/sdk/ai/azure-ai-agents/src/main/java/com/azure/ai/agents/ResponsesAsyncClient.java index a783c71712c6..cacb550d7990 100644 --- a/sdk/ai/azure-ai-agents/src/main/java/com/azure/ai/agents/ResponsesAsyncClient.java +++ b/sdk/ai/azure-ai-agents/src/main/java/com/azure/ai/agents/ResponsesAsyncClient.java @@ -14,7 +14,6 @@ import com.openai.client.OpenAIClientAsync; import com.openai.core.JsonValue; import com.openai.core.RequestOptions; -import com.openai.core.http.HttpResponse; import com.openai.core.http.HttpResponseFor; import com.openai.core.http.StreamResponse; import com.openai.models.responses.Response; @@ -155,63 +154,4 @@ public Mono> createResponseWithResponse(BinaryData cre return Mono.fromFuture(this.responseServiceAsync.withRawResponse() .createStreaming(params, requestOptions == null ? RequestOptions.none() : requestOptions)); } - - /** - * Retrieves a previously created response by id and returns the raw HTTP response. - * - *

Delegates to the OpenAI Java SDK's - * {@link ResponseServiceAsync.WithRawResponse#retrieve(String, RequestOptions)}.

- * - * @param responseId the id of the response to retrieve. - * @param requestOptions optional OpenAI request options; pass {@code null} to use the defaults. - * @return a {@link Mono} emitting the raw HTTP response, parseable as a {@link Response}. - */ - @ServiceMethod(returns = ReturnType.SINGLE) - public Mono> getResponseWithResponse(String responseId, RequestOptions requestOptions) { - Objects.requireNonNull(responseId, "responseId cannot be null"); - - return Mono.fromFuture(this.responseServiceAsync.withRawResponse() - .retrieve(responseId, requestOptions == null ? RequestOptions.none() : requestOptions)); - } - - /** - * Deletes a previously created response and returns the raw HTTP response. - * - *

Delegates to the OpenAI Java SDK's - * {@link ResponseServiceAsync.WithRawResponse#delete(String, RequestOptions)}.

- * - *

The returned {@link HttpResponse} exposes the status code and headers; the body (if any) - * can be read via {@link HttpResponse#body()}. Callers must close the response to release the - * underlying connection.

- * - * @param responseId the id of the response to delete. - * @param requestOptions optional OpenAI request options; pass {@code null} to use the defaults. - * @return a {@link Mono} emitting the raw HTTP response. - */ - @ServiceMethod(returns = ReturnType.SINGLE) - public Mono deleteResponseWithResponse(String responseId, RequestOptions requestOptions) { - Objects.requireNonNull(responseId, "responseId cannot be null"); - - return Mono.fromFuture(this.responseServiceAsync.withRawResponse() - .delete(responseId, requestOptions == null ? RequestOptions.none() : requestOptions)); - } - - /** - * Cancels a previously created response and returns the raw HTTP response. - * - *

Delegates to the OpenAI Java SDK's - * {@link ResponseServiceAsync.WithRawResponse#cancel(String, RequestOptions)}.

- * - * @param responseId the id of the response to cancel. - * @param requestOptions optional OpenAI request options; pass {@code null} to use the defaults. - * @return a {@link Mono} emitting the raw HTTP response, parseable as a {@link Response}. - */ - @ServiceMethod(returns = ReturnType.SINGLE) - public Mono> cancelResponseWithResponse(String responseId, - RequestOptions requestOptions) { - Objects.requireNonNull(responseId, "responseId cannot be null"); - - return Mono.fromFuture(this.responseServiceAsync.withRawResponse() - .cancel(responseId, requestOptions == null ? RequestOptions.none() : requestOptions)); - } } diff --git a/sdk/ai/azure-ai-agents/src/main/java/com/azure/ai/agents/ResponsesClient.java b/sdk/ai/azure-ai-agents/src/main/java/com/azure/ai/agents/ResponsesClient.java index 96b695109d3e..7352c73ae68d 100644 --- a/sdk/ai/azure-ai-agents/src/main/java/com/azure/ai/agents/ResponsesClient.java +++ b/sdk/ai/azure-ai-agents/src/main/java/com/azure/ai/agents/ResponsesClient.java @@ -16,7 +16,6 @@ import com.openai.client.OpenAIClient; import com.openai.core.JsonValue; import com.openai.core.RequestOptions; -import com.openai.core.http.HttpResponse; import com.openai.core.http.HttpResponseFor; import com.openai.core.http.StreamResponse; import com.openai.models.responses.Response; @@ -166,62 +165,4 @@ public HttpResponseFor createResponseWithResponse(BinaryData createRes .createStreaming(params, requestOptions == null ? RequestOptions.none() : requestOptions); } - /** - * Retrieves a previously created response by id and returns the raw HTTP response. - * - *

Delegates to the OpenAI Java SDK's - * {@link ResponseService.WithRawResponse#retrieve(String, RequestOptions)}.

- * - * @param responseId the id of the response to retrieve. - * @param requestOptions optional OpenAI request options; pass {@code null} to use the defaults. - * @return the raw HTTP response, parseable as a {@link Response}. - */ - @ServiceMethod(returns = ReturnType.SINGLE) - public HttpResponseFor getResponseWithResponse(String responseId, RequestOptions requestOptions) { - Objects.requireNonNull(responseId, "responseId cannot be null"); - - return this.responseService.withRawResponse() - .retrieve(responseId, requestOptions == null ? RequestOptions.none() : requestOptions); - } - - /** - * Deletes a previously created response and returns the raw HTTP response. - * - *

Delegates to the OpenAI Java SDK's - * {@link ResponseService.WithRawResponse#delete(String, RequestOptions)}.

- * - *

The returned {@link HttpResponse} exposes the status code and headers; the body (if any) - * can be read via {@link HttpResponse#body()}. Callers must close the response to release the - * underlying connection.

- * - * @param responseId the id of the response to delete. - * @param requestOptions optional OpenAI request options; pass {@code null} to use the defaults. - * @return the raw HTTP response. - */ - @ServiceMethod(returns = ReturnType.SINGLE) - public HttpResponse deleteResponseWithResponse(String responseId, RequestOptions requestOptions) { - Objects.requireNonNull(responseId, "responseId cannot be null"); - - return this.responseService.withRawResponse() - .delete(responseId, requestOptions == null ? RequestOptions.none() : requestOptions); - } - - /** - * Cancels a previously created response and returns the raw HTTP response. - * - *

Delegates to the OpenAI Java SDK's - * {@link ResponseService.WithRawResponse#cancel(String, RequestOptions)}.

- * - * @param responseId the id of the response to cancel. - * @param requestOptions optional OpenAI request options; pass {@code null} to use the defaults. - * @return the raw HTTP response, parseable as a {@link Response}. - */ - @ServiceMethod(returns = ReturnType.SINGLE) - public HttpResponseFor cancelResponseWithResponse(String responseId, RequestOptions requestOptions) { - Objects.requireNonNull(responseId, "responseId cannot be null"); - - return this.responseService.withRawResponse() - .cancel(responseId, requestOptions == null ? RequestOptions.none() : requestOptions); - } - } diff --git a/sdk/ai/azure-ai-agents/src/test/java/com/azure/ai/agents/ResponsesAsyncTests.java b/sdk/ai/azure-ai-agents/src/test/java/com/azure/ai/agents/ResponsesAsyncTests.java index 6ec0fd5db280..19905bf2d67a 100644 --- a/sdk/ai/azure-ai-agents/src/test/java/com/azure/ai/agents/ResponsesAsyncTests.java +++ b/sdk/ai/azure-ai-agents/src/test/java/com/azure/ai/agents/ResponsesAsyncTests.java @@ -5,11 +5,12 @@ import com.azure.core.http.HttpClient; import com.azure.core.util.BinaryData; -import com.openai.core.http.HttpResponse; import com.openai.core.http.HttpResponseFor; +import com.openai.core.http.StreamResponse; import com.openai.models.responses.Response; import com.openai.models.responses.ResponseCreateParams; import com.openai.models.responses.ResponseStatus; +import com.openai.models.responses.ResponseStreamEvent; import com.openai.models.responses.inputitems.InputItemListPageAsync; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; @@ -27,6 +28,9 @@ public class ResponsesAsyncTests extends ClientTestBase { private static final String CREATE_RESPONSE_BODY = "{\"input\":\"Hello, how can you help me?\",\"model\":\"gpt-4o\"}"; + private static final String CREATE_STREAM_RESPONSE_BODY + = "{\"input\":\"Hello, how can you help me?\",\"model\":\"gpt-4o\",\"stream\":true}"; + private static final String CREATE_BACKGROUND_RESPONSE_BODY = "{\"input\":\"Tell me a very long story about a chicken trying to cross the road.\"," + "\"model\":\"gpt-4o\",\"background\":true}"; @@ -89,47 +93,26 @@ public void cancelBackgroundResponse(HttpClient httpClient, AgentsServiceVersion public void basicCRUDOperationsWithResponse(HttpClient httpClient, AgentsServiceVersion serviceVersion) { ResponsesAsyncClient client = getResponsesAsyncClient(httpClient, serviceVersion); - // create HttpResponseFor createdRaw = client.createResponseWithResponse(BinaryData.fromString(CREATE_RESPONSE_BODY), null).block(); assertNotNull(createdRaw); Response createdResponse = createdRaw.parse(); assertNotNull(createdResponse); assertNotNull(createdResponse.id()); - - // retrieve - HttpResponseFor retrievedRaw = client.getResponseWithResponse(createdResponse.id(), null).block(); - assertNotNull(retrievedRaw); - Response retrievedResponse = retrievedRaw.parse(); - assertNotNull(retrievedResponse); - assertEquals(createdResponse.id(), retrievedResponse.id()); - - // delete - HttpResponse deletedRaw = client.deleteResponseWithResponse(createdResponse.id(), null).block(); - assertNotNull(deletedRaw); } @ParameterizedTest(name = DISPLAY_NAME_WITH_ARGUMENTS) @MethodSource("com.azure.ai.agents.TestUtils#getTestParameters") - public void cancelBackgroundResponseWithResponse(HttpClient httpClient, AgentsServiceVersion serviceVersion) { + public void basicStreamingOperationsWithResponse(HttpClient httpClient, AgentsServiceVersion serviceVersion) { ResponsesAsyncClient client = getResponsesAsyncClient(httpClient, serviceVersion); - HttpResponseFor backgroundRaw - = client.createResponseWithResponse(BinaryData.fromString(CREATE_BACKGROUND_RESPONSE_BODY), null).block(); - assertNotNull(backgroundRaw); - Response backgroundResponse = backgroundRaw.parse(); - assertNotNull(backgroundResponse); - assertNotNull(backgroundResponse.id()); - - HttpResponseFor cancelledRaw - = client.cancelResponseWithResponse(backgroundResponse.id(), null).block(); - assertNotNull(cancelledRaw); - Response cancelledResponse = cancelledRaw.parse(); - assertNotNull(cancelledResponse); - assertEquals(backgroundResponse.id(), cancelledResponse.id()); - assertTrue(cancelledResponse.status().isPresent()); - ResponseStatus status = cancelledResponse.status().get(); - assertTrue(status.equals(ResponseStatus.CANCELLED) || status.equals(ResponseStatus.COMPLETED), - "Expected CANCELLED or COMPLETED status but was " + status); + try (HttpResponseFor> raw + = client.createResponseStreamWithResponse(BinaryData.fromString(CREATE_STREAM_RESPONSE_BODY), null).block(); + StreamResponse events = raw.parse()) { + assertNotNull(raw); + assertNotNull(events); + long count = events.stream().peek(e -> assertNotNull(e)).count(); + assertTrue(count > 0, "Expected at least one stream event but received none"); + } } } diff --git a/sdk/ai/azure-ai-agents/src/test/java/com/azure/ai/agents/ResponsesTests.java b/sdk/ai/azure-ai-agents/src/test/java/com/azure/ai/agents/ResponsesTests.java index 319b3aad5422..10bf91aaae9a 100644 --- a/sdk/ai/azure-ai-agents/src/test/java/com/azure/ai/agents/ResponsesTests.java +++ b/sdk/ai/azure-ai-agents/src/test/java/com/azure/ai/agents/ResponsesTests.java @@ -5,11 +5,12 @@ import com.azure.core.http.HttpClient; import com.azure.core.util.BinaryData; -import com.openai.core.http.HttpResponse; import com.openai.core.http.HttpResponseFor; +import com.openai.core.http.StreamResponse; import com.openai.models.responses.Response; import com.openai.models.responses.ResponseCreateParams; import com.openai.models.responses.ResponseStatus; +import com.openai.models.responses.ResponseStreamEvent; import com.openai.models.responses.inputitems.InputItemListPage; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; @@ -25,6 +26,9 @@ public class ResponsesTests extends ClientTestBase { private static final String CREATE_RESPONSE_BODY = "{\"input\":\"Hello, how can you help me?\",\"model\":\"gpt-4o\"}"; + private static final String CREATE_STREAM_RESPONSE_BODY + = "{\"input\":\"Hello, how can you help me?\",\"model\":\"gpt-4o\",\"stream\":true}"; + private static final String CREATE_BACKGROUND_RESPONSE_BODY = "{\"input\":\"Tell me a very long story about a chicken trying to cross the road.\"," + "\"model\":\"gpt-4o\",\"background\":true}"; @@ -85,46 +89,27 @@ public void cancelBackgroundResponse(HttpClient httpClient, AgentsServiceVersion public void basicCRUDOperationsWithResponse(HttpClient httpClient, AgentsServiceVersion serviceVersion) { ResponsesClient client = getResponsesSyncClient(httpClient, serviceVersion); - // create HttpResponseFor createdRaw = client.createResponseWithResponse(BinaryData.fromString(CREATE_RESPONSE_BODY), null); assertNotNull(createdRaw); Response createdResponse = createdRaw.parse(); assertNotNull(createdResponse); assertNotNull(createdResponse.id()); - - // retrieve - HttpResponseFor retrievedRaw = client.getResponseWithResponse(createdResponse.id(), null); - assertNotNull(retrievedRaw); - Response retrievedResponse = retrievedRaw.parse(); - assertNotNull(retrievedResponse); - assertEquals(createdResponse.id(), retrievedResponse.id()); - - // delete - HttpResponse deletedRaw = client.deleteResponseWithResponse(createdResponse.id(), null); - assertNotNull(deletedRaw); } @ParameterizedTest(name = DISPLAY_NAME_WITH_ARGUMENTS) @MethodSource("com.azure.ai.agents.TestUtils#getTestParameters") - public void cancelBackgroundResponseWithResponse(HttpClient httpClient, AgentsServiceVersion serviceVersion) { + public void basicStreamingOperationsWithResponse(HttpClient httpClient, AgentsServiceVersion serviceVersion) { ResponsesClient client = getResponsesSyncClient(httpClient, serviceVersion); - HttpResponseFor backgroundRaw - = client.createResponseWithResponse(BinaryData.fromString(CREATE_BACKGROUND_RESPONSE_BODY), null); - assertNotNull(backgroundRaw); - Response backgroundResponse = backgroundRaw.parse(); - assertNotNull(backgroundResponse); - assertNotNull(backgroundResponse.id()); - - HttpResponseFor cancelledRaw = client.cancelResponseWithResponse(backgroundResponse.id(), null); - assertNotNull(cancelledRaw); - Response cancelledResponse = cancelledRaw.parse(); - assertNotNull(cancelledResponse); - assertEquals(backgroundResponse.id(), cancelledResponse.id()); - assertTrue(cancelledResponse.status().isPresent()); - ResponseStatus status = cancelledResponse.status().get(); - assertTrue(status.equals(ResponseStatus.CANCELLED) || status.equals(ResponseStatus.COMPLETED), - "Expected CANCELLED or COMPLETED status but was " + status); + try ( + HttpResponseFor> raw + = client.createResponseStreamWithResponse(BinaryData.fromString(CREATE_STREAM_RESPONSE_BODY), null); + StreamResponse events = raw.parse()) { + assertNotNull(raw); + assertNotNull(events); + long count = events.stream().peek(e -> assertNotNull(e)).count(); + assertTrue(count > 0, "Expected at least one stream event but received none"); + } } } From f9691942f47e25c4f119418beb1a23e7fb474f93 Mon Sep 17 00:00:00 2001 From: Jose Alvarez Date: Fri, 22 May 2026 13:30:50 +0200 Subject: [PATCH 5/6] Use OpenAI client directly for response get/delete/cancel in tests ResponsesClient and ResponsesAsyncClient are thin Azure-aware wrappers, so non-Azure operations (retrieve, delete, cancel) should go through the underlying com.openai client directly rather than the wrappers' service getters. Adds getResponseServiceSyncClient/getResponseServiceAsyncClient helpers in ClientTestBase that build the OpenAIClient/OpenAIClientAsync and expose .responses(). Updates basicCRUDOperations and cancelBackgroundResponse in both test classes to use the new helpers for retrieve/delete/cancel; create still flows through ResponsesClient. Recordings unchanged (the OpenAI client built by AgentsClientBuilder shares the same Azure HTTP pipeline as the wrappers' service). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../java/com/azure/ai/agents/ClientTestBase.java | 12 ++++++++++++ .../com/azure/ai/agents/ResponsesAsyncTests.java | 9 ++++++--- .../java/com/azure/ai/agents/ResponsesTests.java | 9 ++++++--- 3 files changed, 24 insertions(+), 6 deletions(-) diff --git a/sdk/ai/azure-ai-agents/src/test/java/com/azure/ai/agents/ClientTestBase.java b/sdk/ai/azure-ai-agents/src/test/java/com/azure/ai/agents/ClientTestBase.java index 04ea90051026..2a7665179e1e 100644 --- a/sdk/ai/azure-ai-agents/src/test/java/com/azure/ai/agents/ClientTestBase.java +++ b/sdk/ai/azure-ai-agents/src/test/java/com/azure/ai/agents/ClientTestBase.java @@ -15,7 +15,9 @@ import com.azure.core.util.Configuration; import com.azure.identity.DefaultAzureCredentialBuilder; import com.openai.services.async.ConversationServiceAsync; +import com.openai.services.async.ResponseServiceAsync; import com.openai.services.blocking.ConversationService; +import com.openai.services.blocking.ResponseService; import java.util.ArrayList; import java.util.Arrays; @@ -84,6 +86,16 @@ protected ResponsesAsyncClient getResponsesAsyncClient(HttpClient httpClient, return getClientBuilder(httpClient, agentsServiceVersion).buildResponsesAsyncClient(); } + protected ResponseService getResponseServiceSyncClient(HttpClient httpClient, + AgentsServiceVersion agentsServiceVersion) { + return getClientBuilder(httpClient, agentsServiceVersion).buildOpenAIClient().responses(); + } + + protected ResponseServiceAsync getResponseServiceAsyncClient(HttpClient httpClient, + AgentsServiceVersion agentsServiceVersion) { + return getClientBuilder(httpClient, agentsServiceVersion).buildOpenAIAsyncClient().responses(); + } + protected MemoryStoresClient getMemoryStoresSyncClient(HttpClient httpClient, AgentsServiceVersion agentsServiceVersion) { return getClientBuilder(httpClient, agentsServiceVersion).buildMemoryStoresClient(); diff --git a/sdk/ai/azure-ai-agents/src/test/java/com/azure/ai/agents/ResponsesAsyncTests.java b/sdk/ai/azure-ai-agents/src/test/java/com/azure/ai/agents/ResponsesAsyncTests.java index 19905bf2d67a..76f0414436cd 100644 --- a/sdk/ai/azure-ai-agents/src/test/java/com/azure/ai/agents/ResponsesAsyncTests.java +++ b/sdk/ai/azure-ai-agents/src/test/java/com/azure/ai/agents/ResponsesAsyncTests.java @@ -12,6 +12,7 @@ import com.openai.models.responses.ResponseStatus; import com.openai.models.responses.ResponseStreamEvent; import com.openai.models.responses.inputitems.InputItemListPageAsync; +import com.openai.services.async.ResponseServiceAsync; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; @@ -40,6 +41,7 @@ public class ResponsesAsyncTests extends ClientTestBase { public void basicCRUDOperations(HttpClient httpClient, AgentsServiceVersion serviceVersion) throws ExecutionException, InterruptedException { ResponsesAsyncClient client = getResponsesAsyncClient(httpClient, serviceVersion); + ResponseServiceAsync responseService = getResponseServiceAsyncClient(httpClient, serviceVersion); // create ResponseCreateParams createRequest @@ -49,7 +51,7 @@ public void basicCRUDOperations(HttpClient httpClient, AgentsServiceVersion serv assertNotNull(createdResponse.id()); // retrieve - Response retrievedResponse = client.getResponseServiceAsync().retrieve(createdResponse.id()).get(); + Response retrievedResponse = responseService.retrieve(createdResponse.id()).get(); assertNotNull(retrievedResponse); assertEquals(createdResponse.id(), retrievedResponse.id()); @@ -61,7 +63,7 @@ public void basicCRUDOperations(HttpClient httpClient, AgentsServiceVersion serv assertFalse(inputItems.data().isEmpty()); // delete - client.getResponseServiceAsync().delete(createdResponse.id()).get(); + responseService.delete(createdResponse.id()).get(); } @ParameterizedTest(name = DISPLAY_NAME_WITH_ARGUMENTS) @@ -69,6 +71,7 @@ public void basicCRUDOperations(HttpClient httpClient, AgentsServiceVersion serv public void cancelBackgroundResponse(HttpClient httpClient, AgentsServiceVersion serviceVersion) throws ExecutionException, InterruptedException { ResponsesAsyncClient client = getResponsesAsyncClient(httpClient, serviceVersion); + ResponseServiceAsync responseService = getResponseServiceAsyncClient(httpClient, serviceVersion); ResponseCreateParams createRequest = ResponseCreateParams.builder() .input("Tell me a very long story about a chicken trying to cross the road.") @@ -79,7 +82,7 @@ public void cancelBackgroundResponse(HttpClient httpClient, AgentsServiceVersion assertNotNull(backgroundResponse); assertNotNull(backgroundResponse.id()); - Response cancelledResponse = client.getResponseServiceAsync().cancel(backgroundResponse.id()).get(); + Response cancelledResponse = responseService.cancel(backgroundResponse.id()).get(); assertNotNull(cancelledResponse); assertEquals(backgroundResponse.id(), cancelledResponse.id()); assertTrue(cancelledResponse.status().isPresent()); diff --git a/sdk/ai/azure-ai-agents/src/test/java/com/azure/ai/agents/ResponsesTests.java b/sdk/ai/azure-ai-agents/src/test/java/com/azure/ai/agents/ResponsesTests.java index 10bf91aaae9a..1d7b4078fd16 100644 --- a/sdk/ai/azure-ai-agents/src/test/java/com/azure/ai/agents/ResponsesTests.java +++ b/sdk/ai/azure-ai-agents/src/test/java/com/azure/ai/agents/ResponsesTests.java @@ -12,6 +12,7 @@ import com.openai.models.responses.ResponseStatus; import com.openai.models.responses.ResponseStreamEvent; import com.openai.models.responses.inputitems.InputItemListPage; +import com.openai.services.blocking.ResponseService; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; @@ -37,6 +38,7 @@ public class ResponsesTests extends ClientTestBase { @MethodSource("com.azure.ai.agents.TestUtils#getTestParameters") public void basicCRUDOperations(HttpClient httpClient, AgentsServiceVersion serviceVersion) { ResponsesClient client = getResponsesSyncClient(httpClient, serviceVersion); + ResponseService responseService = getResponseServiceSyncClient(httpClient, serviceVersion); // create ResponseCreateParams createRequest @@ -46,7 +48,7 @@ public void basicCRUDOperations(HttpClient httpClient, AgentsServiceVersion serv assertNotNull(createdResponse.id()); // retrieve - Response retrievedResponse = client.getResponseService().retrieve(createdResponse.id()); + Response retrievedResponse = responseService.retrieve(createdResponse.id()); assertNotNull(retrievedResponse); assertEquals(createdResponse.id(), retrievedResponse.id()); @@ -57,13 +59,14 @@ public void basicCRUDOperations(HttpClient httpClient, AgentsServiceVersion serv assertFalse(inputItems.data().isEmpty()); // delete - client.getResponseService().delete(createdResponse.id()); + responseService.delete(createdResponse.id()); } @ParameterizedTest(name = DISPLAY_NAME_WITH_ARGUMENTS) @MethodSource("com.azure.ai.agents.TestUtils#getTestParameters") public void cancelBackgroundResponse(HttpClient httpClient, AgentsServiceVersion serviceVersion) { ResponsesClient client = getResponsesSyncClient(httpClient, serviceVersion); + ResponseService responseService = getResponseServiceSyncClient(httpClient, serviceVersion); ResponseCreateParams createRequest = ResponseCreateParams.builder() .input("Tell me a very long story about a chicken trying to cross the road.") @@ -74,7 +77,7 @@ public void cancelBackgroundResponse(HttpClient httpClient, AgentsServiceVersion assertNotNull(backgroundResponse); assertNotNull(backgroundResponse.id()); - Response cancelledResponse = client.getResponseService().cancel(backgroundResponse.id()); + Response cancelledResponse = responseService.cancel(backgroundResponse.id()); assertNotNull(cancelledResponse); assertEquals(backgroundResponse.id(), cancelledResponse.id()); assertTrue(cancelledResponse.status().isPresent()); From cbc691df5a23fb3db23fdc13a84e7f48705dc7ef Mon Sep 17 00:00:00 2001 From: Jose Alvarez Date: Fri, 22 May 2026 14:07:43 +0200 Subject: [PATCH 6/6] Address PR review feedback on Responses protocol methods - Reword JavaDoc on createResponseWithResponse in both clients to clarify the JSON body is forwarded semantically (parsed and re-serialized via additionalBodyProperties), not byte-for-byte, since property ordering may change and duplicate top-level keys are not preserved. - Apply the same clarification to the streaming variant JavaDoc. - Wrap HttpResponseFor in try-with-resources in basicCRUDOperationsWithResponse for both sync and async test classes so the response is reliably closed. - Remove unused CREATE_BACKGROUND_RESPONSE_BODY constant in both test classes. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../azure/ai/agents/ResponsesAsyncClient.java | 10 +++++++--- .../com/azure/ai/agents/ResponsesClient.java | 10 +++++++--- .../azure/ai/agents/ResponsesAsyncTests.java | 17 +++++++---------- .../com/azure/ai/agents/ResponsesTests.java | 17 +++++++---------- 4 files changed, 28 insertions(+), 26 deletions(-) diff --git a/sdk/ai/azure-ai-agents/src/main/java/com/azure/ai/agents/ResponsesAsyncClient.java b/sdk/ai/azure-ai-agents/src/main/java/com/azure/ai/agents/ResponsesAsyncClient.java index cacb550d7990..07f98c4a6d64 100644 --- a/sdk/ai/azure-ai-agents/src/main/java/com/azure/ai/agents/ResponsesAsyncClient.java +++ b/sdk/ai/azure-ai-agents/src/main/java/com/azure/ai/agents/ResponsesAsyncClient.java @@ -94,8 +94,10 @@ public Flux createStreamingAzureResponse(AzureCreateRespons * *

This protocol method delegates to the OpenAI Java SDK's * {@link ResponseServiceAsync.WithRawResponse#create(ResponseCreateParams, RequestOptions)}. The - * {@code createResponseRequest} payload is forwarded verbatim as the request body, so callers can - * include Azure-specific extensions (such as + * {@code createResponseRequest} payload is forwarded as the request body (semantically, not + * byte-for-byte: the JSON is parsed and re-serialized via {@code additionalBodyProperties}, + * so property ordering and exact formatting may change and duplicate top-level keys are not + * preserved), so callers can include Azure-specific extensions (such as * {@link com.azure.ai.agents.models.AgentReference}) without going through the strongly-typed * {@link ResponseCreateParams.Builder}.

* @@ -130,7 +132,9 @@ public Mono> createResponseWithResponse(BinaryData cre * *

Delegates to the OpenAI Java SDK's * {@link ResponseServiceAsync.WithRawResponse#createStreaming(ResponseCreateParams, RequestOptions)}. - * The {@code createResponseRequest} payload is forwarded verbatim as the request body.

+ * The {@code createResponseRequest} payload is forwarded as the request body (semantically, + * not byte-for-byte: the JSON is parsed and re-serialized, so property ordering and exact + * formatting may change and duplicate top-level keys are not preserved).

* *

The returned {@link HttpResponseFor} wraps a {@link StreamResponse} of * {@link ResponseStreamEvent} items, which the caller iterates via {@link HttpResponseFor#parse()}. diff --git a/sdk/ai/azure-ai-agents/src/main/java/com/azure/ai/agents/ResponsesClient.java b/sdk/ai/azure-ai-agents/src/main/java/com/azure/ai/agents/ResponsesClient.java index 7352c73ae68d..f179f0192fe2 100644 --- a/sdk/ai/azure-ai-agents/src/main/java/com/azure/ai/agents/ResponsesClient.java +++ b/sdk/ai/azure-ai-agents/src/main/java/com/azure/ai/agents/ResponsesClient.java @@ -106,8 +106,10 @@ public static AzureCreateResponseDetails getAzureFields(Response response) { * *

This protocol method delegates to the OpenAI Java SDK's * {@link ResponseService.WithRawResponse#create(ResponseCreateParams, RequestOptions)}. The - * {@code createResponseRequest} payload is forwarded verbatim as the request body, so callers can - * include Azure-specific extensions (such as + * {@code createResponseRequest} payload is forwarded as the request body (semantically, not + * byte-for-byte: the JSON is parsed and re-serialized via {@code additionalBodyProperties}, + * so property ordering and exact formatting may change and duplicate top-level keys are not + * preserved), so callers can include Azure-specific extensions (such as * {@link com.azure.ai.agents.models.AgentReference}) without going through the strongly-typed * {@link ResponseCreateParams.Builder}.

* @@ -142,7 +144,9 @@ public HttpResponseFor createResponseWithResponse(BinaryData createRes * *

Delegates to the OpenAI Java SDK's * {@link ResponseService.WithRawResponse#createStreaming(ResponseCreateParams, RequestOptions)}. - * The {@code createResponseRequest} payload is forwarded verbatim as the request body.

+ * The {@code createResponseRequest} payload is forwarded as the request body (semantically, + * not byte-for-byte: the JSON is parsed and re-serialized, so property ordering and exact + * formatting may change and duplicate top-level keys are not preserved).

* *

The returned {@link HttpResponseFor} wraps a {@link StreamResponse} of * {@link ResponseStreamEvent} items, which the caller iterates via {@link HttpResponseFor#parse()}. diff --git a/sdk/ai/azure-ai-agents/src/test/java/com/azure/ai/agents/ResponsesAsyncTests.java b/sdk/ai/azure-ai-agents/src/test/java/com/azure/ai/agents/ResponsesAsyncTests.java index 76f0414436cd..e9e7d064f368 100644 --- a/sdk/ai/azure-ai-agents/src/test/java/com/azure/ai/agents/ResponsesAsyncTests.java +++ b/sdk/ai/azure-ai-agents/src/test/java/com/azure/ai/agents/ResponsesAsyncTests.java @@ -32,10 +32,6 @@ public class ResponsesAsyncTests extends ClientTestBase { private static final String CREATE_STREAM_RESPONSE_BODY = "{\"input\":\"Hello, how can you help me?\",\"model\":\"gpt-4o\",\"stream\":true}"; - private static final String CREATE_BACKGROUND_RESPONSE_BODY - = "{\"input\":\"Tell me a very long story about a chicken trying to cross the road.\"," - + "\"model\":\"gpt-4o\",\"background\":true}"; - @ParameterizedTest(name = DISPLAY_NAME_WITH_ARGUMENTS) @MethodSource("com.azure.ai.agents.TestUtils#getTestParameters") public void basicCRUDOperations(HttpClient httpClient, AgentsServiceVersion serviceVersion) @@ -96,12 +92,13 @@ public void cancelBackgroundResponse(HttpClient httpClient, AgentsServiceVersion public void basicCRUDOperationsWithResponse(HttpClient httpClient, AgentsServiceVersion serviceVersion) { ResponsesAsyncClient client = getResponsesAsyncClient(httpClient, serviceVersion); - HttpResponseFor createdRaw - = client.createResponseWithResponse(BinaryData.fromString(CREATE_RESPONSE_BODY), null).block(); - assertNotNull(createdRaw); - Response createdResponse = createdRaw.parse(); - assertNotNull(createdResponse); - assertNotNull(createdResponse.id()); + try (HttpResponseFor createdRaw + = client.createResponseWithResponse(BinaryData.fromString(CREATE_RESPONSE_BODY), null).block()) { + assertNotNull(createdRaw); + Response createdResponse = createdRaw.parse(); + assertNotNull(createdResponse); + assertNotNull(createdResponse.id()); + } } @ParameterizedTest(name = DISPLAY_NAME_WITH_ARGUMENTS) diff --git a/sdk/ai/azure-ai-agents/src/test/java/com/azure/ai/agents/ResponsesTests.java b/sdk/ai/azure-ai-agents/src/test/java/com/azure/ai/agents/ResponsesTests.java index 1d7b4078fd16..5eb0c3d6b1da 100644 --- a/sdk/ai/azure-ai-agents/src/test/java/com/azure/ai/agents/ResponsesTests.java +++ b/sdk/ai/azure-ai-agents/src/test/java/com/azure/ai/agents/ResponsesTests.java @@ -30,10 +30,6 @@ public class ResponsesTests extends ClientTestBase { private static final String CREATE_STREAM_RESPONSE_BODY = "{\"input\":\"Hello, how can you help me?\",\"model\":\"gpt-4o\",\"stream\":true}"; - private static final String CREATE_BACKGROUND_RESPONSE_BODY - = "{\"input\":\"Tell me a very long story about a chicken trying to cross the road.\"," - + "\"model\":\"gpt-4o\",\"background\":true}"; - @ParameterizedTest(name = DISPLAY_NAME_WITH_ARGUMENTS) @MethodSource("com.azure.ai.agents.TestUtils#getTestParameters") public void basicCRUDOperations(HttpClient httpClient, AgentsServiceVersion serviceVersion) { @@ -92,12 +88,13 @@ public void cancelBackgroundResponse(HttpClient httpClient, AgentsServiceVersion public void basicCRUDOperationsWithResponse(HttpClient httpClient, AgentsServiceVersion serviceVersion) { ResponsesClient client = getResponsesSyncClient(httpClient, serviceVersion); - HttpResponseFor createdRaw - = client.createResponseWithResponse(BinaryData.fromString(CREATE_RESPONSE_BODY), null); - assertNotNull(createdRaw); - Response createdResponse = createdRaw.parse(); - assertNotNull(createdResponse); - assertNotNull(createdResponse.id()); + try (HttpResponseFor createdRaw + = client.createResponseWithResponse(BinaryData.fromString(CREATE_RESPONSE_BODY), null)) { + assertNotNull(createdRaw); + Response createdResponse = createdRaw.parse(); + assertNotNull(createdResponse); + assertNotNull(createdResponse.id()); + } } @ParameterizedTest(name = DISPLAY_NAME_WITH_ARGUMENTS)