Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions sdk/ai/azure-ai-agents/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<Response>`) and `createResponseStreamWithResponse` (returns `HttpResponseFor<StreamResponse<ResponseStreamEvent>>`). 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
Expand Down
2 changes: 1 addition & 1 deletion sdk/ai/azure-ai-agents/assets.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -84,4 +88,74 @@ public Flux<ResponseStreamEvent> 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.
*
* <p>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}.</p>
*
* <p>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.</p>
*
* <p>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).</p>
*
* @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<HttpResponseFor<Response>> 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.
*
* <p>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).</p>
*
* <p>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.</p>
*
* @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<HttpResponseFor<StreamResponse<ResponseStreamEvent>>>
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));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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.
*
* <p>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}.</p>
*
* <p>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.</p>
*
* <p>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).</p>
*
* @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<Response> 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.
*
* <p>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).</p>
*
* <p>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}.</p>
*
* @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<StreamResponse<ResponseStreamEvent>>
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);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -160,18 +160,43 @@ public static <T extends JsonSerializable<T>> Map<String, JsonValue> toJsonValue
}
try {
String json = BinaryData.fromObject(obj).toString();
Map<String, Object> map = MAPPER.readValue(json, new TypeReference<Map<String, Object>>() {
});
Map<String, JsonValue> result = new HashMap<>();
for (Map.Entry<String, Object> 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<String, JsonValue> 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<String, JsonValue> jsonStringToJsonValueMap(String json) throws IOException {
Map<String, Object> map = MAPPER.readValue(json, new TypeReference<Map<String, Object>>() {
});
Map<String, JsonValue> result = new HashMap<>();
for (Map.Entry<String, Object> 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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();
Expand Down
Loading