diff --git a/sdk/ai/azure-ai-agents/CHANGELOG.md b/sdk/ai/azure-ai-agents/CHANGELOG.md index 384fe0fe67a4..c4d2b8c93af7 100644 --- a/sdk/ai/azure-ai-agents/CHANGELOG.md +++ b/sdk/ai/azure-ai-agents/CHANGELOG.md @@ -16,6 +16,10 @@ - `createStreamingWithAgent` and `createStreamingWithAgentConversation` on `ResponsesAsyncClient` return `Flux` for asynchronous streaming. - Added `StreamingUtils` implementation helper that bridges OpenAI `StreamResponse` to `IterableStream` and `AsyncStreamResponse` to `Flux`. - Added streaming samples: `SimpleStreamingSync`/`SimpleStreamingAsync`, `FunctionCallStreamingSync`/`FunctionCallStreamingAsync`, and `CodeInterpreterStreamingSync`/`CodeInterpreterStreamingAsync`. +- Added structured input convenience methods to `ResponsesClient` and `ResponsesAsyncClient` for creating responses with agent-defined template parameters: + - `createWithAgentStructuredInput` accepts a `Map` of runtime values that are substituted into the agent's prompt template. + - `createStreamingWithAgentStructuredInput` provides the streaming equivalent, returning `IterableStream` (sync) or `Flux` (async). +- Added `CreateResponseWithStructuredInput` sample demonstrating how to define structured inputs on an agent and pass runtime values when creating a response. ### Breaking Changes @@ -33,6 +37,7 @@ - Added `ToolsTests` and `ToolsAsyncTests` with recorded end-to-end test coverage for OpenAPI, Code Interpreter, Function Call, Web Search, MCP, and File Search tools. - Added `StreamingTests` and `StreamingAsyncTests` with recorded test coverage for streaming responses (simple prompt, function calling, and Code Interpreter scenarios). +- Added structured input test coverage to `AgentsTests`, `AgentsAsyncTests`, `StreamingTests`, and `StreamingAsyncTests`. ## 2.0.0-beta.2 (2026-03-04) diff --git a/sdk/ai/azure-ai-agents/README.md b/sdk/ai/azure-ai-agents/README.md index b269f8955235..02b3eda657b9 100644 --- a/sdk/ai/azure-ai-agents/README.md +++ b/sdk/ai/azure-ai-agents/README.md @@ -623,6 +623,51 @@ See the full samples in [SimpleStreamingAsync.java](https://github.com/Azure/azu --- +### Structured inputs + +Structured inputs allow you to define named parameters on an agent that get substituted into its prompt template at runtime. This is useful when you want the same agent definition to handle different users or contexts by simply changing the input values. + +#### Define structured inputs on an agent + +When creating the agent, declare each structured input with a description and whether it is required. Use `{{inputName}}` placeholders in the instructions to reference them: + +```java com.azure.ai.agents.define_structured_inputs +// Create an agent with structured input definitions +Map structuredInputDefinitions = new LinkedHashMap<>(); +structuredInputDefinitions.put("userName", new StructuredInputDefinition().setDescription("User's name").setRequired(true)); +structuredInputDefinitions.put("userRole", new StructuredInputDefinition().setDescription("User's role").setRequired(true)); + +AgentVersionDetails agent = agentsClient.createAgentVersion("structured-input-agent", + new PromptAgentDefinition(model) + .setInstructions("You are a helpful assistant. " + + "The user's name is {{userName}} and their role is {{userRole}}. " + + "Greet them and confirm their details.") + .setStructuredInputs(structuredInputDefinitions)); +``` + +#### Create a response with structured input values + +When creating a response, pass a `Map` whose keys match the structured input names declared on the agent. The values are substituted into the prompt template before the model processes the request: + +```java com.azure.ai.agents.create_response_with_structured_input +// Create a response, passing structured input values that match the agent's definitions +Map structuredInputValues = new LinkedHashMap<>(); +structuredInputValues.put("userName", "Alice Smith"); +structuredInputValues.put("userRole", "Senior Developer"); + +Response response = responsesClient.createWithAgentStructuredInput( + new AgentReference(agent.getName()).setVersion(agent.getVersion()), + structuredInputValues, + ResponseCreateParams.builder().input("Hello! Can you confirm my details?") +); +``` + +Streaming is also supported via `createStreamingWithAgentStructuredInput`, which returns an `IterableStream` (sync) or `Flux` (async). + +See the full sample in [CreateResponseWithStructuredInput.java](https://github.com/Azure/azure-sdk-for-java/tree/main/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/CreateResponseWithStructuredInput.java). + +--- + ### Service API versions The client library targets the latest service API version by default. diff --git a/sdk/ai/azure-ai-agents/assets.json b/sdk/ai/azure-ai-agents/assets.json index f30fca7a76ff..b7110f21c7ff 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_4b3a190a4f" + "Tag": "java/ai/azure-ai-agents_bb4574aecb" } 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 46847f198d95..12d4bafd5b9f 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 @@ -111,6 +111,44 @@ public Mono createWithAgent(AgentReference agentReference) { return createWithAgent(agentReference, new ResponseCreateParams.Builder()); } + /** + * Creates a response using structured input values that are substituted into the agent's prompt template + * at runtime. The keys in the {@code structuredInputs} map must match the structured input names declared + * on the agent's definition (via {@link com.azure.ai.agents.models.StructuredInputDefinition}), and the values should conform to the + * schema and type expectations defined there. + * + *

For example, if the agent was created with structured inputs {@code "userName"} and {@code "userRole"}, + * the map should contain the corresponding runtime values:

+ *
{@code
+     * Map structuredInputs = new LinkedHashMap<>();
+     * structuredInputs.put("userName", "Alice Smith");
+     * structuredInputs.put("userRole", "Senior Developer");
+     * }
+ * + * @param agentReference The agent reference. + * @param structuredInputs A map of structured input names to their runtime values. The values are + * serialized as JSON and must match the structure expected by the agent's definition. + * @param params The parameters to create the response. + * @return A Mono that emits the created Response. + */ + public Mono createWithAgentStructuredInput(AgentReference agentReference, + Map structuredInputs, ResponseCreateParams.Builder params) { + Objects.requireNonNull(agentReference, "agentReference cannot be null"); + Objects.requireNonNull(structuredInputs, "structuredInputs cannot be null"); + Objects.requireNonNull(params, "params cannot be null"); + + JsonValue agentRefJsonValue = OpenAIJsonHelper.toJsonValue(agentReference); + JsonValue structuredInputsJsonValue = JsonValue.from(structuredInputs); + + Map additionalBodyProperties = new HashMap<>(); + additionalBodyProperties.put("agent_reference", agentRefJsonValue); + additionalBodyProperties.put("structured_inputs", structuredInputsJsonValue); + + params.additionalBodyProperties(additionalBodyProperties); + + return Mono.fromFuture(this.responseServiceAsync.create(params.build())); + } + /** * Creates a streaming response with an agent. * @@ -132,6 +170,43 @@ public Flux createStreamingWithAgent(AgentReference agentRe return StreamingUtils.toFlux(this.responseServiceAsync.createStreaming(params.build())); } + /** + * Creates a streaming response using structured input values that are substituted into the agent's prompt + * template at runtime. The keys in the {@code structuredInputs} map must match the structured input names + * declared on the agent's definition (via {@link com.azure.ai.agents.models.StructuredInputDefinition}), and the values should conform + * to the schema and type expectations defined there. + * + *

For example, if the agent was created with structured inputs {@code "userName"} and {@code "userRole"}, + * the map should contain the corresponding runtime values:

+ *
{@code
+     * Map structuredInputs = new LinkedHashMap<>();
+     * structuredInputs.put("userName", "Alice Smith");
+     * structuredInputs.put("userRole", "Senior Developer");
+     * }
+ * + * @param agentReference The agent reference. + * @param structuredInputs A map of structured input names to their runtime values. The values are + * serialized as JSON and must match the structure expected by the agent's definition. + * @param params The parameters to create the response. + * @return A Flux of ResponseStreamEvent. + */ + public Flux createStreamingWithAgentStructuredInput(AgentReference agentReference, + Map structuredInputs, ResponseCreateParams.Builder params) { + Objects.requireNonNull(agentReference, "agentReference cannot be null"); + Objects.requireNonNull(structuredInputs, "structuredInputs cannot be null"); + Objects.requireNonNull(params, "params cannot be null"); + + JsonValue agentRefJsonValue = OpenAIJsonHelper.toJsonValue(agentReference); + JsonValue structuredInputsJsonValue = JsonValue.from(structuredInputs); + + Map additionalBodyProperties = new HashMap<>(); + additionalBodyProperties.put("agent_reference", agentRefJsonValue); + additionalBodyProperties.put("structured_inputs", structuredInputsJsonValue); + + params.additionalBodyProperties(additionalBodyProperties); + return StreamingUtils.toFlux(this.responseServiceAsync.createStreaming(params.build())); + } + /** * Creates a streaming response with an agent conversation. * 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 a93aa57e7109..b6f4609b284c 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 @@ -110,6 +110,43 @@ public Response createWithAgent(AgentReference agentReference) { return createWithAgent(agentReference, new ResponseCreateParams.Builder()); } + /** + * Creates a response using structured input values that are substituted into the agent's prompt template + * at runtime. The keys in the {@code structuredInputs} map must match the structured input names declared + * on the agent's definition (via {@link com.azure.ai.agents.models.StructuredInputDefinition}), and the values should conform to the + * schema and type expectations defined there. + * + *

For example, if the agent was created with structured inputs {@code "userName"} and {@code "userRole"}, + * the map should contain the corresponding runtime values:

+ *
{@code
+     * Map structuredInputs = new LinkedHashMap<>();
+     * structuredInputs.put("userName", "Alice Smith");
+     * structuredInputs.put("userRole", "Senior Developer");
+     * }
+ * + * @param agentReference The agent reference. + * @param structuredInputs A map of structured input names to their runtime values. The values are + * serialized as JSON and must match the structure expected by the agent's definition. + * @param params The parameters to create the response. + * @return The created Response. + */ + public Response createWithAgentStructuredInput(AgentReference agentReference, Map structuredInputs, + ResponseCreateParams.Builder params) { + Objects.requireNonNull(agentReference, "agentReference cannot be null"); + Objects.requireNonNull(structuredInputs, "structuredInputs cannot be null"); + + JsonValue agentRefJsonValue = OpenAIJsonHelper.toJsonValue(agentReference); + JsonValue structuredInputsJsonValue = JsonValue.from(structuredInputs); + + Map additionalBodyProperties = new HashMap<>(); + additionalBodyProperties.put("agent_reference", agentRefJsonValue); + additionalBodyProperties.put("structured_inputs", structuredInputsJsonValue); + + params.additionalBodyProperties(additionalBodyProperties); + + return this.responseService.create(params.build()); + } + /** * Creates a streaming response with an agent. * @@ -131,6 +168,43 @@ public IterableStream createStreamingWithAgent(AgentReferen return StreamingUtils.toIterableStream(this.responseService.createStreaming(params.build())); } + /** + * Creates a streaming response using structured input values that are substituted into the agent's prompt + * template at runtime. The keys in the {@code structuredInputs} map must match the structured input names + * declared on the agent's definition (via {@link com.azure.ai.agents.models.StructuredInputDefinition}), and the values should conform + * to the schema and type expectations defined there. + * + *

For example, if the agent was created with structured inputs {@code "userName"} and {@code "userRole"}, + * the map should contain the corresponding runtime values:

+ *
{@code
+     * Map structuredInputs = new LinkedHashMap<>();
+     * structuredInputs.put("userName", "Alice Smith");
+     * structuredInputs.put("userRole", "Senior Developer");
+     * }
+ * + * @param agentReference The agent reference. + * @param structuredInputs A map of structured input names to their runtime values. The values are + * serialized as JSON and must match the structure expected by the agent's definition. + * @param params The parameters to create the response. + * @return An IterableStream of ResponseStreamEvent. + */ + public IterableStream createStreamingWithAgentStructuredInput(AgentReference agentReference, + Map structuredInputs, ResponseCreateParams.Builder params) { + Objects.requireNonNull(agentReference, "agentReference cannot be null"); + Objects.requireNonNull(structuredInputs, "structuredInputs cannot be null"); + Objects.requireNonNull(params, "params cannot be null"); + + JsonValue agentRefJsonValue = OpenAIJsonHelper.toJsonValue(agentReference); + JsonValue structuredInputsJsonValue = JsonValue.from(structuredInputs); + + Map additionalBodyProperties = new HashMap<>(); + additionalBodyProperties.put("agent_reference", agentRefJsonValue); + additionalBodyProperties.put("structured_inputs", structuredInputsJsonValue); + + params.additionalBodyProperties(additionalBodyProperties); + return StreamingUtils.toIterableStream(this.responseService.createStreaming(params.build())); + } + /** * Creates a streaming response with an agent conversation. * diff --git a/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/CreateResponseWithStructuredInput.java b/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/CreateResponseWithStructuredInput.java new file mode 100644 index 000000000000..d44e3934d2d3 --- /dev/null +++ b/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/CreateResponseWithStructuredInput.java @@ -0,0 +1,68 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.ai.agents; + +import com.azure.ai.agents.models.AgentReference; +import com.azure.ai.agents.models.AgentVersionDetails; +import com.azure.ai.agents.models.PromptAgentDefinition; +import com.azure.ai.agents.models.StructuredInputDefinition; +import com.azure.core.util.Configuration; +import com.azure.identity.DefaultAzureCredentialBuilder; +import com.openai.models.responses.Response; +import com.openai.models.responses.ResponseCreateParams; + +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * This sample demonstrates how to create a response with structured inputs. + * Structured inputs are key-value pairs defined on an agent that get substituted + * into the agent's prompt template at runtime. + */ +public class CreateResponseWithStructuredInput { + public static void main(String[] args) { + String endpoint = Configuration.getGlobalConfiguration().get("FOUNDRY_PROJECT_ENDPOINT"); + String model = Configuration.getGlobalConfiguration().get("FOUNDRY_MODEL_NAME"); + + AgentsClientBuilder builder = new AgentsClientBuilder() + .credential(new DefaultAzureCredentialBuilder().build()) + .endpoint(endpoint) + .serviceVersion(AgentsServiceVersion.getLatest()); + + AgentsClient agentsClient = builder.buildAgentsClient(); + ResponsesClient responsesClient = builder.buildResponsesClient(); + + // BEGIN: com.azure.ai.agents.define_structured_inputs + // Create an agent with structured input definitions + Map structuredInputDefinitions = new LinkedHashMap<>(); + structuredInputDefinitions.put("userName", new StructuredInputDefinition().setDescription("User's name").setRequired(true)); + structuredInputDefinitions.put("userRole", new StructuredInputDefinition().setDescription("User's role").setRequired(true)); + + AgentVersionDetails agent = agentsClient.createAgentVersion("structured-input-agent", + new PromptAgentDefinition(model) + .setInstructions("You are a helpful assistant. " + + "The user's name is {{userName}} and their role is {{userRole}}. " + + "Greet them and confirm their details.") + .setStructuredInputs(structuredInputDefinitions)); + // END: com.azure.ai.agents.define_structured_inputs + + // BEGIN: com.azure.ai.agents.create_response_with_structured_input + // Create a response, passing structured input values that match the agent's definitions + Map structuredInputValues = new LinkedHashMap<>(); + structuredInputValues.put("userName", "Alice Smith"); + structuredInputValues.put("userRole", "Senior Developer"); + + Response response = responsesClient.createWithAgentStructuredInput( + new AgentReference(agent.getName()).setVersion(agent.getVersion()), + structuredInputValues, + ResponseCreateParams.builder().input("Hello! Can you confirm my details?") + ); + // END: com.azure.ai.agents.create_response_with_structured_input + + System.out.println("Response: " + response.output()); + + // Cleanup + agentsClient.deleteAgentVersion(agent.getName(), agent.getVersion()); + } +} diff --git a/sdk/ai/azure-ai-agents/src/test/java/com/azure/ai/agents/AgentsAsyncTests.java b/sdk/ai/azure-ai-agents/src/test/java/com/azure/ai/agents/AgentsAsyncTests.java index 6a83cd59fb78..ed6db87b68d2 100644 --- a/sdk/ai/azure-ai-agents/src/test/java/com/azure/ai/agents/AgentsAsyncTests.java +++ b/sdk/ai/azure-ai-agents/src/test/java/com/azure/ai/agents/AgentsAsyncTests.java @@ -5,6 +5,7 @@ import com.azure.ai.agents.models.AgentReference; import com.azure.ai.agents.models.PromptAgentDefinition; +import com.azure.ai.agents.models.StructuredInputDefinition; import com.azure.core.http.HttpClient; import com.openai.models.conversations.Conversation; import com.openai.models.responses.EasyInputMessage; @@ -18,10 +19,15 @@ import reactor.test.StepVerifier; import static com.azure.ai.agents.TestUtils.DISPLAY_NAME_WITH_ARGUMENTS; -import static org.junit.jupiter.api.Assertions.*; +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; import java.util.ArrayList; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; public class AgentsAsyncTests extends ClientTestBase { @@ -159,4 +165,54 @@ private Mono cleanupPromptAgentTest(AgentsAsyncClient agentsClient, // Deleting response causes a 500 in service, but keep the request for parity with sync tests. Mono.fromFuture(responsesClient.getResponseServiceAsync().delete(responseId)).then()); } + + @ParameterizedTest(name = DISPLAY_NAME_WITH_ARGUMENTS) + @MethodSource("com.azure.ai.agents.TestUtils#getTestParameters") + public void structuredInputTest(HttpClient httpClient, AgentsServiceVersion serviceVersion) { + AgentsAsyncClient agentsClient = getAgentsAsyncClient(httpClient, serviceVersion); + ResponsesAsyncClient responsesClient = getResponsesAsyncClient(httpClient, serviceVersion); + String agentModel = "gpt-4o"; + + // Create an agent with structured input definitions + Map structuredInputDefinitions = new LinkedHashMap<>(); + structuredInputDefinitions.put("userName", + new StructuredInputDefinition().setDescription("User's name").setRequired(true)); + structuredInputDefinitions.put("userRole", + new StructuredInputDefinition().setDescription("User's role").setRequired(true)); + + StepVerifier.create( + agentsClient + .createAgentVersion(AGENT_NAME, + new PromptAgentDefinition(agentModel).setInstructions("You are a helpful assistant. " + + "The user's name is {{userName}} and their role is {{userRole}}. " + + "Greet them and confirm their details.").setStructuredInputs(structuredInputDefinitions)) + .flatMap(createdAgent -> { + assertNotNull(createdAgent); + assertNotNull(createdAgent.getId()); + assertEquals(AGENT_NAME, createdAgent.getName()); + + Map structuredInputValues = new LinkedHashMap<>(); + structuredInputValues.put("userName", "Alice Smith"); + structuredInputValues.put("userRole", "Senior Developer"); + + return responsesClient + .createWithAgentStructuredInput( + new AgentReference(createdAgent.getName()).setVersion(createdAgent.getVersion()), + structuredInputValues, + ResponseCreateParams.builder().input("Hello! Can you confirm my details?")) + .flatMap(response -> agentsClient + .deleteAgentVersion(createdAgent.getName(), createdAgent.getVersion()) + .thenReturn(response)); + })) + .assertNext(response -> { + assertNotNull(response); + assertTrue(response.id().startsWith("resp")); + assertTrue(response.status().isPresent()); + assertEquals(ResponseStatus.COMPLETED, response.status().get()); + assertFalse(response.output().isEmpty()); + assertTrue(response.output().get(0).isMessage()); + assertFalse(response.output().get(0).asMessage().content().isEmpty()); + }) + .verifyComplete(); + } } diff --git a/sdk/ai/azure-ai-agents/src/test/java/com/azure/ai/agents/AgentsTests.java b/sdk/ai/azure-ai-agents/src/test/java/com/azure/ai/agents/AgentsTests.java index f1599675af9a..2178934bd9f3 100644 --- a/sdk/ai/azure-ai-agents/src/test/java/com/azure/ai/agents/AgentsTests.java +++ b/sdk/ai/azure-ai-agents/src/test/java/com/azure/ai/agents/AgentsTests.java @@ -10,6 +10,7 @@ import com.azure.ai.agents.models.DeleteAgentResponse; import com.azure.ai.agents.models.DeleteAgentVersionResponse; import com.azure.ai.agents.models.PromptAgentDefinition; +import com.azure.ai.agents.models.StructuredInputDefinition; import com.azure.core.http.HttpClient; import com.openai.models.conversations.Conversation; import com.openai.models.responses.EasyInputMessage; @@ -21,7 +22,10 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; -import java.util.*; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; import static com.azure.ai.agents.TestUtils.DISPLAY_NAME_WITH_ARGUMENTS; import static org.junit.jupiter.api.Assertions.*; @@ -205,4 +209,49 @@ public void promptAgentTest(HttpClient httpClient, AgentsServiceVersion serviceV // // Deleting response causes a 500 // // responsesClient.getOpenAIClient().delete(response.id()); // } + + @ParameterizedTest(name = DISPLAY_NAME_WITH_ARGUMENTS) + @MethodSource("com.azure.ai.agents.TestUtils#getTestParameters") + public void structuredInputTest(HttpClient httpClient, AgentsServiceVersion serviceVersion) { + AgentsClient agentsClient = getAgentsSyncClient(httpClient, serviceVersion); + ResponsesClient responsesClient = getResponsesSyncClient(httpClient, serviceVersion); + String agentModel = "gpt-4o"; + + // Create an agent with structured input definitions + Map structuredInputDefinitions = new LinkedHashMap<>(); + structuredInputDefinitions.put("userName", + new StructuredInputDefinition().setDescription("User's name").setRequired(true)); + structuredInputDefinitions.put("userRole", + new StructuredInputDefinition().setDescription("User's role").setRequired(true)); + + AgentVersionDetails createdAgent = agentsClient.createAgentVersion(AGENT_NAME, + new PromptAgentDefinition(agentModel).setInstructions( + "You are a helpful assistant. " + "The user's name is {{userName}} and their role is {{userRole}}. " + + "Greet them and confirm their details.") + .setStructuredInputs(structuredInputDefinitions)); + + assertNotNull(createdAgent); + assertNotNull(createdAgent.getId()); + assertEquals(AGENT_NAME, createdAgent.getName()); + + // Create a response, passing structured input values that match the agent's definitions + Map structuredInputValues = new LinkedHashMap<>(); + structuredInputValues.put("userName", "Alice Smith"); + structuredInputValues.put("userRole", "Senior Developer"); + + Response response = responsesClient.createWithAgentStructuredInput( + new AgentReference(createdAgent.getName()).setVersion(createdAgent.getVersion()), structuredInputValues, + ResponseCreateParams.builder().input("Hello! Can you confirm my details?")); + + assertNotNull(response); + assertTrue(response.id().startsWith("resp")); + assertTrue(response.status().isPresent()); + assertEquals(ResponseStatus.COMPLETED, response.status().get()); + assertFalse(response.output().isEmpty()); + assertTrue(response.output().get(0).isMessage()); + assertFalse(response.output().get(0).asMessage().content().isEmpty()); + + // Clean up + agentsClient.deleteAgentVersion(createdAgent.getName(), createdAgent.getVersion()); + } } diff --git a/sdk/ai/azure-ai-agents/src/test/java/com/azure/ai/agents/StreamingAsyncTests.java b/sdk/ai/azure-ai-agents/src/test/java/com/azure/ai/agents/StreamingAsyncTests.java index 1b70df8fdb09..08d65fcdf1fd 100644 --- a/sdk/ai/azure-ai-agents/src/test/java/com/azure/ai/agents/StreamingAsyncTests.java +++ b/sdk/ai/azure-ai-agents/src/test/java/com/azure/ai/agents/StreamingAsyncTests.java @@ -8,6 +8,7 @@ import com.azure.ai.agents.models.CodeInterpreterTool; import com.azure.ai.agents.models.FunctionTool; import com.azure.ai.agents.models.PromptAgentDefinition; +import com.azure.ai.agents.models.StructuredInputDefinition; import com.azure.core.http.HttpClient; import com.azure.core.util.BinaryData; import com.openai.helpers.ResponseAccumulator; @@ -204,6 +205,70 @@ public void codeInterpreterStreamingProducesCodeEvents(HttpClient httpClient, Ag .verifyComplete(); } + // ======================================================================== + // Structured input streaming + // ======================================================================== + + @ParameterizedTest(name = DISPLAY_NAME_WITH_ARGUMENTS) + @MethodSource("com.azure.ai.agents.TestUtils#getTestParameters") + public void structuredInputStreamingProducesTextDeltas(HttpClient httpClient, AgentsServiceVersion serviceVersion) { + AgentsAsyncClient agentsClient = getAgentsAsyncClient(httpClient, serviceVersion); + ResponsesAsyncClient responsesClient = getResponsesAsyncClient(httpClient, serviceVersion); + + Map structuredInputDefinitions = new LinkedHashMap<>(); + structuredInputDefinitions.put("userName", + new StructuredInputDefinition().setDescription("User's name").setRequired(true)); + structuredInputDefinitions.put("userRole", + new StructuredInputDefinition().setDescription("User's role").setRequired(true)); + + AtomicReference agentRef = new AtomicReference<>(); + + StepVerifier + .create(agentsClient + .createAgentVersion("structured-input-streaming-async-test-agent", + new PromptAgentDefinition(AGENT_MODEL).setInstructions("You are a helpful assistant. " + + "The user's name is {{userName}} and their role is {{userRole}}. " + + "Greet them and confirm their details.").setStructuredInputs(structuredInputDefinitions)) + .flatMap(agent -> { + agentRef.set(agent); + + AgentReference agentReference = new AgentReference(agent.getName()).setVersion(agent.getVersion()); + + Map structuredInputValues = new LinkedHashMap<>(); + structuredInputValues.put("userName", "Alice Smith"); + structuredInputValues.put("userRole", "Senior Developer"); + + ResponseAccumulator accumulator = ResponseAccumulator.create(); + List textDeltas = new ArrayList<>(); + + Flux events + = responsesClient.createStreamingWithAgentStructuredInput(agentReference, structuredInputValues, + ResponseCreateParams.builder().input("Hello! Can you confirm my details?")); + + return events.doOnNext(event -> { + accumulator.accumulate(event); + event.outputTextDelta().ifPresent(textEvent -> textDeltas.add(textEvent.delta())); + }).then(Mono.fromCallable(() -> { + assertFalse(textDeltas.isEmpty(), "Should have received at least one text delta"); + + Response response = accumulator.response(); + assertNotNull(response.id()); + assertTrue(response.status().isPresent()); + assertEquals(ResponseStatus.COMPLETED, response.status().get()); + + String streamedText = String.join("", textDeltas); + assertFalse(streamedText.isEmpty()); + return response; + })); + }) + .flatMap(response -> { + AgentVersionDetails agent = agentRef.get(); + return agentsClient.deleteAgentVersion(agent.getName(), agent.getVersion()).thenReturn(response); + })) + .assertNext(response -> assertNotNull(response.id())) + .verifyComplete(); + } + // ======================================================================== // Helpers // ======================================================================== diff --git a/sdk/ai/azure-ai-agents/src/test/java/com/azure/ai/agents/StreamingTests.java b/sdk/ai/azure-ai-agents/src/test/java/com/azure/ai/agents/StreamingTests.java index d1aab10da950..055aabc93687 100644 --- a/sdk/ai/azure-ai-agents/src/test/java/com/azure/ai/agents/StreamingTests.java +++ b/sdk/ai/azure-ai-agents/src/test/java/com/azure/ai/agents/StreamingTests.java @@ -8,6 +8,7 @@ import com.azure.ai.agents.models.CodeInterpreterTool; import com.azure.ai.agents.models.FunctionTool; import com.azure.ai.agents.models.PromptAgentDefinition; +import com.azure.ai.agents.models.StructuredInputDefinition; import com.azure.core.http.HttpClient; import com.azure.core.util.BinaryData; import com.azure.core.util.IterableStream; @@ -184,6 +185,62 @@ public void codeInterpreterStreamingProducesCodeEvents(HttpClient httpClient, Ag } } + // ======================================================================== + // Structured input streaming + // ======================================================================== + + @ParameterizedTest(name = DISPLAY_NAME_WITH_ARGUMENTS) + @MethodSource("com.azure.ai.agents.TestUtils#getTestParameters") + public void structuredInputStreamingProducesTextDeltas(HttpClient httpClient, AgentsServiceVersion serviceVersion) { + AgentsClient agentsClient = getAgentsSyncClient(httpClient, serviceVersion); + ResponsesClient responsesClient = getResponsesSyncClient(httpClient, serviceVersion); + + // Create an agent with structured input definitions + Map structuredInputDefinitions = new LinkedHashMap<>(); + structuredInputDefinitions.put("userName", + new StructuredInputDefinition().setDescription("User's name").setRequired(true)); + structuredInputDefinitions.put("userRole", + new StructuredInputDefinition().setDescription("User's role").setRequired(true)); + + AgentVersionDetails agent = agentsClient.createAgentVersion("structured-input-streaming-test-agent", + new PromptAgentDefinition(AGENT_MODEL).setInstructions( + "You are a helpful assistant. " + "The user's name is {{userName}} and their role is {{userRole}}. " + + "Greet them and confirm their details.") + .setStructuredInputs(structuredInputDefinitions)); + + try { + AgentReference agentReference = new AgentReference(agent.getName()).setVersion(agent.getVersion()); + + Map structuredInputValues = new LinkedHashMap<>(); + structuredInputValues.put("userName", "Alice Smith"); + structuredInputValues.put("userRole", "Senior Developer"); + + ResponseAccumulator accumulator = ResponseAccumulator.create(); + List textDeltas = new ArrayList<>(); + + IterableStream events + = responsesClient.createStreamingWithAgentStructuredInput(agentReference, structuredInputValues, + ResponseCreateParams.builder().input("Hello! Can you confirm my details?")); + + for (ResponseStreamEvent event : events) { + accumulator.accumulate(event); + event.outputTextDelta().ifPresent(textEvent -> textDeltas.add(textEvent.delta())); + } + + assertFalse(textDeltas.isEmpty(), "Should have received at least one text delta"); + + Response response = accumulator.response(); + assertNotNull(response.id()); + assertTrue(response.status().isPresent()); + assertEquals(ResponseStatus.COMPLETED, response.status().get()); + + String streamedText = String.join("", textDeltas); + assertFalse(streamedText.isEmpty()); + } finally { + agentsClient.deleteAgentVersion(agent.getName(), agent.getVersion()); + } + } + // ======================================================================== // Helpers // ========================================================================