diff --git a/sdk/ai/azure-ai-agents/CHANGELOG.md b/sdk/ai/azure-ai-agents/CHANGELOG.md index 6a3c9a299b51..527ff8a8e270 100644 --- a/sdk/ai/azure-ai-agents/CHANGELOG.md +++ b/sdk/ai/azure-ai-agents/CHANGELOG.md @@ -4,6 +4,12 @@ ### 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. 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 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 ### Bugs Fixed diff --git a/sdk/ai/azure-ai-agents/assets.json b/sdk/ai/azure-ai-agents/assets.json index 87e9dbe90acc..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_69308f6d56" + "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 3ebcace05ea9..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 @@ -10,8 +10,12 @@ 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.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 +88,74 @@ 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 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}.

+ * + *

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 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()}. + * 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)); + } } 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..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 @@ -11,9 +11,13 @@ 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.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 +101,72 @@ 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 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}.

+ * + *

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 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()}. + * 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); + } + } 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 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 2d12d824cc57..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 @@ -4,29 +4,115 @@ 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.core.http.StreamResponse; 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.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; 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_STREAM_RESPONSE_BODY + = "{\"input\":\"Hello, how can you help me?\",\"model\":\"gpt-4o\",\"stream\":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); + ResponseServiceAsync responseService = getResponseServiceAsyncClient(httpClient, serviceVersion); + + // 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 = responseService.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()); + + // delete + responseService.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); + ResponseServiceAsync responseService = getResponseServiceAsyncClient(httpClient, serviceVersion); - ResponseCreateParams responsesRequest - = new ResponseCreateParams.Builder().input("Hello, how can you help me?").model("gpt-4o").build(); + 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 = responseService.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) + @MethodSource("com.azure.ai.agents.TestUtils#getTestParameters") + public void basicCRUDOperationsWithResponse(HttpClient httpClient, AgentsServiceVersion serviceVersion) { + ResponsesAsyncClient client = getResponsesAsyncClient(httpClient, serviceVersion); + + 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) + @MethodSource("com.azure.ai.agents.TestUtils#getTestParameters") + public void basicStreamingOperationsWithResponse(HttpClient httpClient, AgentsServiceVersion serviceVersion) { + ResponsesAsyncClient client = getResponsesAsyncClient(httpClient, serviceVersion); - Response response = client.getResponseServiceAsync().create(responsesRequest).get(); - System.out.println(response); + 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 49459f16b2fb..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 @@ -4,59 +4,112 @@ 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.core.http.StreamResponse; 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.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; 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_STREAM_RESPONSE_BODY + = "{\"input\":\"Hello, how can you help me?\",\"model\":\"gpt-4o\",\"stream\":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); + ResponseService responseService = getResponseServiceSyncClient(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 = responseService.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 + 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.") + .model("gpt-4o") + .background(true) + .build(); + Response backgroundResponse = client.getResponseService().create(createRequest); + assertNotNull(backgroundResponse); + assertNotNull(backgroundResponse.id()); + + Response cancelledResponse = responseService.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); + } + + @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); + + 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) + @MethodSource("com.azure.ai.agents.TestUtils#getTestParameters") + public void basicStreamingOperationsWithResponse(HttpClient httpClient, AgentsServiceVersion serviceVersion) { + ResponsesClient client = getResponsesSyncClient(httpClient, serviceVersion); + + 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"); + } } }