From ba1d0d22cf6a2235728ec97e5d7ee54c0c76c05c Mon Sep 17 00:00:00 2001 From: Mike Guo Date: Wed, 2 Sep 2026 13:40:50 +0800 Subject: [PATCH 1/4] Add Azure AI Agents samples and parity tests Co-Authored-By: Claude --- sdk/ai/azure-ai-agents/README.md | 49 ++- sdk/ai/azure-ai-agents/pom.xml | 24 ++ .../ai/agents/AgentBasicAsyncSample.java | 123 ++++++ .../agents/AgentRetrieveBasicAsyncSample.java | 88 ++++ .../AgentStructuredOutputAsyncSample.java | 53 +++ .../agents/AgentStructuredOutputSample.java | 92 +++++ .../CreateResponseWithConversation.java | 102 ++--- .../java/com/azure/ai/agents/SampleUtils.java | 53 ++- .../agents/WorkflowMultiAgentAsyncSample.java | 78 ++++ .../WorkflowMultiAgentMcpApprovalSample.java | 96 +++++ .../ai/agents/WorkflowMultiAgentSample.java | 68 ++++ .../azure/ai/agents/WorkflowSampleUtils.java | 46 +++ .../com/azure/ai/agents/agents/GetAgent.java | 43 +- .../agents/conversations/GetConversation.java | 36 +- ...ptimizationAdvancedPollingAsyncSample.java | 79 ++++ ...gentOptimizationAdvancedPollingSample.java | 68 ++++ .../AgentOptimizationCancelSample.java | 62 +++ .../AgentOptimizationListGetDeleteSample.java | 59 +++ .../AgentOptimizationSampleUtils.java | 40 ++ .../AgentAzureMonitorTracingSample.java | 47 +++ ...tConsoleTracingCustomAttributesSample.java | 49 +++ .../telemetry/AgentConsoleTracingSample.java | 45 +++ .../telemetry/AgentTelemetrySampleUtils.java | 57 +++ .../ToolboxSearchAgentAsyncSample.java | 98 +++++ .../toolboxes/ToolboxSearchToolboxSample.java | 108 +++-- .../CodeInterpreterStructuredInputsSync.java | 92 +++++ .../tools/CodeInterpreterWithFilesAsync.java | 93 +++++ .../tools/CodeInterpreterWithFilesSync.java | 100 +++++ .../tools/FileSearchStreamingAsync.java | 105 +++++ .../agents/tools/FileSearchStreamingSync.java | 97 +++++ .../tools/FileSearchStructuredInputsSync.java | 104 +++++ .../ai/agents/tools/ToolSampleUtils.java | 90 +++++ .../tools/WebSearchCustomSearchSync.java | 71 ++++ .../azure/ai/agents/tools/WebSearchSync.java | 74 ++-- .../ai/agents/tools/WebSearchToolSync.java | 69 ++++ .../voice/VoiceAgentBasicAsyncSample.java | 64 +++ .../agents/voice/VoiceAgentBasicSample.java | 67 +++ .../voice/VoiceAgentGenerateSample.java | 54 +++ ...VoiceAgentReadConversationAudioSample.java | 78 ++++ .../VoiceAgentReadConversationSample.java | 67 +++ .../agents/voice/VoiceAgentSampleUtils.java | 29 ++ .../voice/VoiceAgentVersionsSample.java | 69 ++++ .../voice/VoiceAgentWithToolsSample.java | 96 +++++ .../agents/AgentOptimizationPollerTests.java | 92 +++++ .../ai/agents/AgentProtocolMockTests.java | 217 ++++++++++ .../ai/agents/DeterministicHttpClient.java | 79 ++++ ...FoundryFeaturesHeaderVerificationTest.java | 52 +++ .../ai/agents/MultiToolBehaviorMockTests.java | 240 +++++++++++ .../ai/agents/OpenAIResponseFixtures.java | 38 ++ .../agents/PortableToolBehaviorMockTests.java | 382 ++++++++++++++++++ .../ai/agents/ToolStreamingMockTests.java | 147 +++++++ .../azure/ai/agents/VoiceAgentsMockTests.java | 225 +++++++++++ .../ai/agents/VoiceConversationMockTests.java | 122 ++++++ ...ncedAgentDefinitionSerializationTests.java | 168 ++++++++ ...oiceAgentDefinitionSerializationTests.java | 102 +++++ 55 files changed, 4768 insertions(+), 178 deletions(-) create mode 100644 sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/AgentBasicAsyncSample.java create mode 100644 sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/AgentRetrieveBasicAsyncSample.java create mode 100644 sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/AgentStructuredOutputAsyncSample.java create mode 100644 sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/AgentStructuredOutputSample.java create mode 100644 sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/WorkflowMultiAgentAsyncSample.java create mode 100644 sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/WorkflowMultiAgentMcpApprovalSample.java create mode 100644 sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/WorkflowMultiAgentSample.java create mode 100644 sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/WorkflowSampleUtils.java create mode 100644 sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/optimization/AgentOptimizationAdvancedPollingAsyncSample.java create mode 100644 sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/optimization/AgentOptimizationAdvancedPollingSample.java create mode 100644 sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/optimization/AgentOptimizationCancelSample.java create mode 100644 sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/optimization/AgentOptimizationListGetDeleteSample.java create mode 100644 sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/optimization/AgentOptimizationSampleUtils.java create mode 100644 sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/telemetry/AgentAzureMonitorTracingSample.java create mode 100644 sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/telemetry/AgentConsoleTracingCustomAttributesSample.java create mode 100644 sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/telemetry/AgentConsoleTracingSample.java create mode 100644 sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/telemetry/AgentTelemetrySampleUtils.java create mode 100644 sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/toolboxes/ToolboxSearchAgentAsyncSample.java create mode 100644 sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/tools/CodeInterpreterStructuredInputsSync.java create mode 100644 sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/tools/CodeInterpreterWithFilesAsync.java create mode 100644 sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/tools/CodeInterpreterWithFilesSync.java create mode 100644 sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/tools/FileSearchStreamingAsync.java create mode 100644 sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/tools/FileSearchStreamingSync.java create mode 100644 sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/tools/FileSearchStructuredInputsSync.java create mode 100644 sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/tools/ToolSampleUtils.java create mode 100644 sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/tools/WebSearchCustomSearchSync.java create mode 100644 sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/tools/WebSearchToolSync.java create mode 100644 sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/voice/VoiceAgentBasicAsyncSample.java create mode 100644 sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/voice/VoiceAgentBasicSample.java create mode 100644 sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/voice/VoiceAgentGenerateSample.java create mode 100644 sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/voice/VoiceAgentReadConversationAudioSample.java create mode 100644 sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/voice/VoiceAgentReadConversationSample.java create mode 100644 sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/voice/VoiceAgentSampleUtils.java create mode 100644 sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/voice/VoiceAgentVersionsSample.java create mode 100644 sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/voice/VoiceAgentWithToolsSample.java create mode 100644 sdk/ai/azure-ai-agents/src/test/java/com/azure/ai/agents/AgentOptimizationPollerTests.java create mode 100644 sdk/ai/azure-ai-agents/src/test/java/com/azure/ai/agents/AgentProtocolMockTests.java create mode 100644 sdk/ai/azure-ai-agents/src/test/java/com/azure/ai/agents/DeterministicHttpClient.java create mode 100644 sdk/ai/azure-ai-agents/src/test/java/com/azure/ai/agents/MultiToolBehaviorMockTests.java create mode 100644 sdk/ai/azure-ai-agents/src/test/java/com/azure/ai/agents/OpenAIResponseFixtures.java create mode 100644 sdk/ai/azure-ai-agents/src/test/java/com/azure/ai/agents/PortableToolBehaviorMockTests.java create mode 100644 sdk/ai/azure-ai-agents/src/test/java/com/azure/ai/agents/ToolStreamingMockTests.java create mode 100644 sdk/ai/azure-ai-agents/src/test/java/com/azure/ai/agents/VoiceAgentsMockTests.java create mode 100644 sdk/ai/azure-ai-agents/src/test/java/com/azure/ai/agents/VoiceConversationMockTests.java create mode 100644 sdk/ai/azure-ai-agents/src/test/java/com/azure/ai/agents/models/AdvancedAgentDefinitionSerializationTests.java create mode 100644 sdk/ai/azure-ai-agents/src/test/java/com/azure/ai/agents/models/VoiceAgentDefinitionSerializationTests.java diff --git a/sdk/ai/azure-ai-agents/README.md b/sdk/ai/azure-ai-agents/README.md index 67001ef341ed8..a978449eae701 100644 --- a/sdk/ai/azure-ai-agents/README.md +++ b/sdk/ai/azure-ai-agents/README.md @@ -339,8 +339,11 @@ See the full sample in [ImageGenerationSync.java](https://github.com/Azure/azure Search the web for current information: ```java com.azure.ai.agents.define_web_search -// Create a WebSearchPreviewTool -WebSearchPreviewTool tool = new WebSearchPreviewTool(); +WebSearchPreviewTool tool = new WebSearchPreviewTool() + .setUserLocation(new ApproximateLocation() + .setCountry("GB") + .setRegion("London") + .setCity("London")); ``` See the full sample in [WebSearchSync.java](https://github.com/Azure/azure-sdk-for-java/tree/main/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/tools/WebSearchSync.java). @@ -680,25 +683,14 @@ Toolbox tools are defined in toolbox versions and managed through `ToolboxesClie Toolbox Search lets an agent search the available toolbox tools at runtime. The GA implementation is `ToolSearchToolboxTool` (`toolbox_search`), and the preview implementation `ToolboxSearchPreviewToolboxTool` (`toolbox_search_preview`) is maintained alongside it for backward compatibility. ```java com.azure.ai.agents.toolboxes.ToolboxSearchToolboxSample.createToolboxSearchToolbox - -ToolSearchToolboxTool toolboxSearchTool = new ToolSearchToolboxTool() - .setName("search_tools") - .setDescription("Search over available toolbox tools at runtime."); - -ToolboxVersionDetails version = toolboxesClient.createToolboxVersion( - toolboxName, - Collections.singletonList(toolboxSearchTool), - "Toolbox version with a Toolbox Search tool.", - null, - null, - null); - -System.out.printf("Created toolbox: %s%n", version.getName()); -System.out.printf("Toolbox version: %s%n", version.getVersion()); -for (ToolboxTool tool : version.getTools()) { - System.out.printf("Tool type: %s%n", tool.getType()); -} - +McpToolboxTool innerMcp = new McpToolboxTool("github") + .setServerUrl("https://api.githubcopilot.com/mcp") + .setProjectConnectionId(configuration.get("MCP_PROJECT_CONNECTION_ID")) + .setRequireApproval(BinaryData.fromString("\"never\"")) + .setDeferLoading(true); +ToolSearchToolboxTool search = new ToolSearchToolboxTool(); +ToolboxVersionDetails version = toolboxesClient.createToolboxVersion(toolboxName, + Arrays.asList(innerMcp, search), "Tool-search toolbox", null, null, null); ``` See the full sample in [ToolboxSearchToolboxSample.java](https://github.com/Azure/azure-sdk-for-java/tree/main/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/toolboxes/ToolboxSearchToolboxSample.java). @@ -880,6 +872,21 @@ See the full sample in [CreateResponseWithStructuredInput.java](https://github.c --- +### Additional end-to-end samples + +All agent samples use `FOUNDRY_PROJECT_ENDPOINT`. Prompt-agent samples also use `FOUNDRY_MODEL_NAME`, while voice-agent samples use `FOUNDRY_VOICE_MODEL` and optionally `FOUNDRY_VOICE_AGENT_NAME`. + +- **Agent lifecycle and structured output:** [AgentBasicAsyncSample.java](https://github.com/Azure/azure-sdk-for-java/tree/main/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/AgentBasicAsyncSample.java), [AgentRetrieveBasicAsyncSample.java](https://github.com/Azure/azure-sdk-for-java/tree/main/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/AgentRetrieveBasicAsyncSample.java), and the `AgentStructuredOutput*` samples. +- **Workflow agents:** `WorkflowMultiAgentSample`, `WorkflowMultiAgentAsyncSample`, and `WorkflowMultiAgentMcpApprovalSample` demonstrate CSDL workflows and MCP approval handling. +- **Optimization jobs:** the [optimization samples](https://github.com/Azure/azure-sdk-for-java/tree/main/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/optimization) cover SDK polling, application-managed polling, cancellation, listing, retrieval, and deletion. +- **Telemetry:** the [telemetry samples](https://github.com/Azure/azure-sdk-for-java/tree/main/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/telemetry) demonstrate console tracing, custom span attributes, and Azure Monitor export. +- **Advanced tools:** additional samples cover structured inputs, generated-file download, File Search streaming, non-preview Web Search, custom search, and end-to-end toolbox search. +- **Voice agents:** the [voice samples](https://github.com/Azure/azure-sdk-for-java/tree/main/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/voice) cover lifecycle, versions and drafts, guided generation, tool-rich definitions, persisted conversation readback, and audio download. + +Live voice text/audio sessions are not included because the current Java client exposes the WebSocket handshake but not the bidirectional realtime event-session abstraction required by those scenarios. + +--- + ### Service API versions The client library targets the latest service API version by default. diff --git a/sdk/ai/azure-ai-agents/pom.xml b/sdk/ai/azure-ai-agents/pom.xml index 5f2af5fd12fe2..93926bb1d3a17 100644 --- a/sdk/ai/azure-ai-agents/pom.xml +++ b/sdk/ai/azure-ai-agents/pom.xml @@ -88,6 +88,30 @@ 1.27.0-beta.18 test + + com.azure + azure-core-tracing-opentelemetry + 1.0.0-beta.66 + test + + + com.azure + azure-monitor-opentelemetry-autoconfigure + 1.6.0 + test + + + io.opentelemetry + opentelemetry-sdk-extension-autoconfigure + 1.58.0 + test + + + io.opentelemetry + opentelemetry-exporter-logging + 1.58.0 + test + diff --git a/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/AgentBasicAsyncSample.java b/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/AgentBasicAsyncSample.java new file mode 100644 index 0000000000000..09602fb99b52c --- /dev/null +++ b/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/AgentBasicAsyncSample.java @@ -0,0 +1,123 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.ai.agents; + +import com.azure.ai.agents.models.AgentDetails; +import com.azure.ai.agents.models.AgentEndpointConfig; +import com.azure.ai.agents.models.AgentVersionDetails; +import com.azure.ai.agents.models.AzureCreateResponseOptions; +import com.azure.ai.agents.models.CreateAgentVersionInput; +import com.azure.ai.agents.models.FixedRatioVersionSelectionRule; +import com.azure.ai.agents.models.PromptAgentDefinition; +import com.azure.ai.agents.models.ProtocolConfiguration; +import com.azure.ai.agents.models.ResponsesProtocolConfiguration; +import com.azure.ai.agents.models.UpdateAgentDetailsOptions; +import com.azure.ai.agents.models.VersionSelector; +import com.azure.core.util.Configuration; +import com.azure.identity.DefaultAzureCredentialBuilder; +import com.openai.models.conversations.items.ItemCreateParams; +import com.openai.models.responses.EasyInputMessage; +import com.openai.models.responses.ResponseCreateParams; +import com.openai.services.async.ConversationServiceAsync; +import reactor.core.publisher.Mono; + +import java.util.Collections; +import java.util.concurrent.atomic.AtomicReference; + +/** + * Demonstrates asynchronous prompt-agent creation, endpoint routing, and a multi-turn conversation. + * + *

Before running the sample, set these environment variables:

+ *
    + *
  • {@code FOUNDRY_PROJECT_ENDPOINT} - The Azure AI Project endpoint.
  • + *
  • {@code FOUNDRY_MODEL_NAME} - The model deployment name.
  • + *
+ */ +public class AgentBasicAsyncSample { + public static void main(String[] args) { + Configuration configuration = Configuration.getGlobalConfiguration(); + String endpoint = configuration.get("FOUNDRY_PROJECT_ENDPOINT"); + String model = configuration.get("FOUNDRY_MODEL_NAME"); + + AgentsClientBuilder builder = new AgentsClientBuilder() + .credential(new DefaultAzureCredentialBuilder().build()) + .endpoint(endpoint); + AgentsAsyncClient agentsClient = builder.buildAgentsAsyncClient(); + ResponsesAsyncClient responsesClient = builder.buildResponsesAsyncClient(); + ConversationServiceAsync conversations = builder.buildOpenAIAsyncClient().conversations(); + + String agentName = "basic-async-agent"; + AtomicReference agentRef = new AtomicReference<>(); + AtomicReference originalEndpointRef = new AtomicReference<>(); + AtomicReference conversationIdRef = new AtomicReference<>(); + + Mono sample = agentsClient.createAgentVersion(agentName, + new CreateAgentVersionInput(new PromptAgentDefinition(model) + .setInstructions("You are a helpful assistant that answers general questions."))) + .doOnNext(agent -> { + agentRef.set(agent); + System.out.printf("Agent created: %s (version %s)%n", agent.getName(), agent.getVersion()); + }) + .flatMap(agent -> agentsClient.getAgent(agentName) + .doOnNext(details -> originalEndpointRef.set(details.getAgentEndpoint())) + .thenReturn(agent)) + .flatMap(agent -> { + AgentEndpointConfig endpointConfig = new AgentEndpointConfig() + .setVersionSelector(new VersionSelector().setVersionSelectionRules(Collections.singletonList( + new FixedRatioVersionSelectionRule(100).setAgentVersion(agent.getVersion())))) + .setProtocolConfiguration(new ProtocolConfiguration() + .setResponses(new ResponsesProtocolConfiguration())); + return agentsClient.updateAgentDetails(agentName, + new UpdateAgentDetailsOptions().setAgentEndpoint(endpointConfig)); + }) + .then(Mono.fromFuture(conversations.create())) + .doOnNext(conversation -> { + conversationIdRef.set(conversation.id()); + System.out.println("Conversation created: " + conversation.id()); + }) + .flatMap(conversation -> responsesClient.createAzureResponse( + new AzureCreateResponseOptions().setAgentReference( + SampleUtils.toAgentReference(agentRef.get())), + ResponseCreateParams.builder() + .conversation(conversation.id()) + .input("What is the size of France in square miles?"))) + .doOnNext(SampleUtils::printResponseText) + .flatMap(response -> Mono.fromFuture(conversations.items().create(ItemCreateParams.builder() + .conversationId(conversationIdRef.get()) + .addItem(EasyInputMessage.builder() + .role(EasyInputMessage.Role.USER) + .content("What is its capital city?") + .build()) + .build()))) + .then(responsesClient.createAzureResponse( + new AzureCreateResponseOptions().setAgentReference( + SampleUtils.toAgentReference(agentRef.get())), + ResponseCreateParams.builder().conversation(conversationIdRef.get()))) + .doOnNext(SampleUtils::printResponseText) + .then(); + + sample.then(cleanup(agentsClient, conversations, agentName, agentRef, originalEndpointRef, + conversationIdRef)) + .onErrorResume(error -> cleanup(agentsClient, conversations, agentName, agentRef, + originalEndpointRef, conversationIdRef) + .then(Mono.error(error))) + .block(); + } + + private static Mono cleanup(AgentsAsyncClient agentsClient, ConversationServiceAsync conversations, + String agentName, AtomicReference agentRef, + AtomicReference originalEndpointRef, AtomicReference conversationIdRef) { + Mono deleteConversation = conversationIdRef.get() == null + ? Mono.empty() + : Mono.fromFuture(conversations.delete(conversationIdRef.get())).then(); + Mono restoreEndpoint = agentRef.get() == null + ? Mono.empty() + : agentsClient.updateAgentDetails(agentName, + new UpdateAgentDetailsOptions().setAgentEndpoint(originalEndpointRef.get())); + Mono deleteVersion = agentRef.get() == null + ? Mono.empty() + : agentsClient.deleteAgentVersion(agentName, agentRef.get().getVersion()); + return deleteConversation.then(restoreEndpoint).then(deleteVersion); + } +} diff --git a/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/AgentRetrieveBasicAsyncSample.java b/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/AgentRetrieveBasicAsyncSample.java new file mode 100644 index 0000000000000..9d76e209bc6e2 --- /dev/null +++ b/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/AgentRetrieveBasicAsyncSample.java @@ -0,0 +1,88 @@ +// 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.AzureCreateResponseOptions; +import com.azure.ai.agents.models.CreateAgentVersionInput; +import com.azure.ai.agents.models.PromptAgentDefinition; +import com.azure.core.util.Configuration; +import com.azure.identity.DefaultAzureCredentialBuilder; +import com.openai.models.conversations.items.ItemCreateParams; +import com.openai.models.responses.EasyInputMessage; +import com.openai.models.responses.ResponseCreateParams; +import com.openai.services.async.ConversationServiceAsync; +import reactor.core.publisher.Mono; + +import java.util.concurrent.atomic.AtomicReference; + +/** + * Demonstrates asynchronously retrieving an agent and conversation before creating a response. + * + *

Before running the sample, set these environment variables:

+ *
    + *
  • {@code FOUNDRY_PROJECT_ENDPOINT} - The Azure AI Project endpoint.
  • + *
  • {@code FOUNDRY_MODEL_NAME} - The model deployment name.
  • + *
+ */ +public class AgentRetrieveBasicAsyncSample { + public static void main(String[] args) { + Configuration configuration = Configuration.getGlobalConfiguration(); + String endpoint = configuration.get("FOUNDRY_PROJECT_ENDPOINT"); + String model = configuration.get("FOUNDRY_MODEL_NAME"); + + AgentsClientBuilder builder = new AgentsClientBuilder() + .credential(new DefaultAzureCredentialBuilder().build()) + .endpoint(endpoint); + AgentsAsyncClient agentsClient = builder.buildAgentsAsyncClient(); + ResponsesAsyncClient responsesClient = builder.buildResponsesAsyncClient(); + ConversationServiceAsync conversations = builder.buildOpenAIAsyncClient().conversations(); + + AtomicReference agentRef = new AtomicReference<>(); + AtomicReference conversationIdRef = new AtomicReference<>(); + + agentsClient.createAgentVersion("retrieve-async-agent", + new CreateAgentVersionInput(new PromptAgentDefinition(model) + .setInstructions("You are a helpful assistant."))) + .doOnNext(agentRef::set) + .flatMap(agent -> agentsClient.getAgent(agent.getName())) + .doOnNext(agent -> System.out.printf("Retrieved agent: %s (%s)%n", agent.getName(), agent.getId())) + .then(Mono.fromFuture(conversations.create())) + .doOnNext(conversation -> conversationIdRef.set(conversation.id())) + .flatMap(conversation -> Mono.fromFuture(conversations.retrieve(conversation.id()))) + .doOnNext(conversation -> System.out.println("Retrieved conversation: " + conversation.id())) + .flatMap(conversation -> Mono.fromFuture(conversations.items().create(ItemCreateParams.builder() + .conversationId(conversation.id()) + .addItem(EasyInputMessage.builder() + .role(EasyInputMessage.Role.USER) + .content("How many feet are in a mile?") + .build()) + .build()))) + .then(Mono.defer(() -> { + AgentVersionDetails agent = agentRef.get(); + AgentReference reference = SampleUtils.toAgentReference(agent); + return responsesClient.createAzureResponse( + new AzureCreateResponseOptions().setAgentReference(reference), + ResponseCreateParams.builder().conversation(conversationIdRef.get())); + })) + .doOnNext(SampleUtils::printResponseText) + .then(cleanup(agentsClient, conversations, agentRef, conversationIdRef)) + .onErrorResume(error -> cleanup(agentsClient, conversations, agentRef, conversationIdRef) + .then(Mono.error(error))) + .block(); + } + + private static Mono cleanup(AgentsAsyncClient agentsClient, ConversationServiceAsync conversations, + AtomicReference agentRef, AtomicReference conversationIdRef) { + Mono deleteConversation = conversationIdRef.get() == null + ? Mono.empty() + : Mono.fromFuture(conversations.delete(conversationIdRef.get())).then(); + AgentVersionDetails agent = agentRef.get(); + Mono deleteAgent = agent == null + ? Mono.empty() + : agentsClient.deleteAgentVersion(agent.getName(), agent.getVersion()); + return deleteConversation.then(deleteAgent); + } +} diff --git a/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/AgentStructuredOutputAsyncSample.java b/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/AgentStructuredOutputAsyncSample.java new file mode 100644 index 0000000000000..77dfbbe711868 --- /dev/null +++ b/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/AgentStructuredOutputAsyncSample.java @@ -0,0 +1,53 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.ai.agents; + +import com.azure.ai.agents.models.AgentVersionDetails; +import com.azure.ai.agents.models.AzureCreateResponseOptions; +import com.azure.ai.agents.models.CreateAgentVersionInput; +import com.azure.core.util.Configuration; +import com.azure.identity.DefaultAzureCredentialBuilder; +import com.openai.models.responses.ResponseCreateParams; +import reactor.core.publisher.Mono; + +import java.util.concurrent.atomic.AtomicReference; + +/** + * Demonstrates asynchronously generating a response that conforms to a JSON schema. + * + *

Before running the sample, set these environment variables:

+ *
    + *
  • {@code FOUNDRY_PROJECT_ENDPOINT} - The Azure AI Project endpoint.
  • + *
  • {@code FOUNDRY_MODEL_NAME} - The model deployment name.
  • + *
+ */ +public class AgentStructuredOutputAsyncSample { + public static void main(String[] args) { + Configuration configuration = Configuration.getGlobalConfiguration(); + String endpoint = configuration.get("FOUNDRY_PROJECT_ENDPOINT"); + String model = configuration.get("FOUNDRY_MODEL_NAME"); + + AgentsClientBuilder builder = new AgentsClientBuilder() + .credential(new DefaultAzureCredentialBuilder().build()) + .endpoint(endpoint); + AgentsAsyncClient agentsClient = builder.buildAgentsAsyncClient(); + ResponsesAsyncClient responsesClient = builder.buildResponsesAsyncClient(); + AtomicReference agentRef = new AtomicReference<>(); + + agentsClient.createAgentVersion("structured-output-async-agent", + new CreateAgentVersionInput(AgentStructuredOutputSample.createDefinition(model))) + .doOnNext(agentRef::set) + .flatMap(agent -> responsesClient.createAzureResponse( + new AzureCreateResponseOptions().setAgentReference(SampleUtils.toAgentReference(agent)), + ResponseCreateParams.builder() + .input("Alice and Bob are going to a science fair on 2026-11-07."))) + .doOnNext(SampleUtils::printResponseText) + .then(Mono.defer(() -> { + AgentVersionDetails agent = agentRef.get(); + return agent == null ? Mono.empty() + : agentsClient.deleteAgentVersion(agent.getName(), agent.getVersion()); + })) + .block(); + } +} diff --git a/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/AgentStructuredOutputSample.java b/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/AgentStructuredOutputSample.java new file mode 100644 index 0000000000000..c564d00f17cf0 --- /dev/null +++ b/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/AgentStructuredOutputSample.java @@ -0,0 +1,92 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.ai.agents; + +import com.azure.ai.agents.models.AgentVersionDetails; +import com.azure.ai.agents.models.AzureCreateResponseOptions; +import com.azure.ai.agents.models.CreateAgentVersionInput; +import com.azure.ai.agents.models.PromptAgentDefinition; +import com.azure.ai.agents.models.PromptAgentDefinitionTextOptions; +import com.azure.ai.agents.models.ResponseFormatJsonSchemaInner; +import com.azure.ai.agents.models.TextResponseFormatJsonSchema; +import com.azure.core.util.BinaryData; +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; + +/** + * Demonstrates generating a response that conforms to a JSON schema. + * + *

Before running the sample, set these environment variables:

+ *
    + *
  • {@code FOUNDRY_PROJECT_ENDPOINT} - The Azure AI Project endpoint.
  • + *
  • {@code FOUNDRY_MODEL_NAME} - The model deployment name.
  • + *
+ */ +public class AgentStructuredOutputSample { + public static void main(String[] args) { + Configuration configuration = Configuration.getGlobalConfiguration(); + String endpoint = configuration.get("FOUNDRY_PROJECT_ENDPOINT"); + String model = configuration.get("FOUNDRY_MODEL_NAME"); + + AgentsClientBuilder builder = new AgentsClientBuilder() + .credential(new DefaultAzureCredentialBuilder().build()) + .endpoint(endpoint); + AgentsClient agentsClient = builder.buildAgentsClient(); + ResponsesClient responsesClient = builder.buildResponsesClient(); + + AgentVersionDetails agent = agentsClient.createAgentVersion("structured-output-agent", + new CreateAgentVersionInput(createDefinition(model))); + try { + Response response = responsesClient.createAzureResponse( + new AzureCreateResponseOptions().setAgentReference(SampleUtils.toAgentReference(agent)), + ResponseCreateParams.builder() + .input("Alice and Bob are going to a science fair on 2026-11-07.")); + SampleUtils.printResponseText(response); + } finally { + agentsClient.deleteAgentVersion(agent.getName(), agent.getVersion()); + } + } + + static PromptAgentDefinition createDefinition(String model) { + Map properties = new LinkedHashMap<>(); + properties.put("name", field("string", null)); + properties.put("date", field("string", "Date in YYYY-MM-DD format")); + properties.put("participants", arrayField("string")); + + Map schema = new LinkedHashMap<>(); + schema.put("type", "object"); + schema.put("properties", properties); + schema.put("required", new String[] { "name", "date", "participants" }); + schema.put("additionalProperties", false); + + ResponseFormatJsonSchemaInner schemaModel = BinaryData.fromObject(schema) + .toObject(ResponseFormatJsonSchemaInner.class); + TextResponseFormatJsonSchema format = new TextResponseFormatJsonSchema("CalendarEvent", schemaModel) + .setStrict(true); + return new PromptAgentDefinition(model) + .setInstructions("Extract calendar event information and return only the requested structured output.") + .setText(new PromptAgentDefinitionTextOptions().setFormat(format)); + } + + private static Map field(String type, String description) { + Map field = new LinkedHashMap<>(); + field.put("type", type); + if (description != null) { + field.put("description", description); + } + return field; + } + + private static Map arrayField(String itemType) { + Map field = new LinkedHashMap<>(); + field.put("type", "array"); + field.put("items", field(itemType, null)); + return field; + } +} diff --git a/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/CreateResponseWithConversation.java b/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/CreateResponseWithConversation.java index 4faf136291ebb..6f4aa332a68b0 100644 --- a/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/CreateResponseWithConversation.java +++ b/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/CreateResponseWithConversation.java @@ -3,90 +3,94 @@ package com.azure.ai.agents; -import com.azure.ai.agents.models.AgentReference; -import com.azure.ai.agents.models.AzureCreateResponseOptions; +import com.azure.ai.agents.models.AgentEndpointConfig; import com.azure.ai.agents.models.AgentVersionDetails; +import com.azure.ai.agents.models.AzureCreateResponseOptions; import com.azure.ai.agents.models.CreateAgentVersionInput; +import com.azure.ai.agents.models.FixedRatioVersionSelectionRule; import com.azure.ai.agents.models.PromptAgentDefinition; +import com.azure.ai.agents.models.ProtocolConfiguration; +import com.azure.ai.agents.models.ResponsesProtocolConfiguration; +import com.azure.ai.agents.models.UpdateAgentDetailsOptions; +import com.azure.ai.agents.models.VersionSelector; import com.azure.core.util.Configuration; import com.azure.identity.DefaultAzureCredentialBuilder; import com.openai.models.conversations.Conversation; +import com.openai.models.conversations.items.ItemCreateParams; +import com.openai.models.responses.EasyInputMessage; import com.openai.models.responses.Response; import com.openai.models.responses.ResponseCreateParams; -import com.openai.models.responses.ResponseOutputItem; -import com.openai.models.responses.ResponseOutputMessage; import com.openai.services.blocking.ConversationService; +import java.util.Collections; + /** - * This sample demonstrates how to use the createWithAgentConversation helper method - * to create a response with a conversation. + * Demonstrates prompt-agent creation, endpoint routing, and a multi-turn conversation. + * + *

Before running the sample, set these environment variables:

+ *
    + *
  • {@code FOUNDRY_PROJECT_ENDPOINT} - The Azure AI Project endpoint.
  • + *
  • {@code FOUNDRY_MODEL_NAME} - The model deployment name.
  • + *
*/ public class CreateResponseWithConversation { public static void main(String[] args) { - String endpoint = Configuration.getGlobalConfiguration().get("FOUNDRY_PROJECT_ENDPOINT"); - String model = Configuration.getGlobalConfiguration().get("FOUNDRY_MODEL_NAME"); + Configuration configuration = Configuration.getGlobalConfiguration(); + String endpoint = configuration.get("FOUNDRY_PROJECT_ENDPOINT"); + String model = configuration.get("FOUNDRY_MODEL_NAME"); AgentsClientBuilder builder = new AgentsClientBuilder() .credential(new DefaultAzureCredentialBuilder().build()) - .serviceVersion(AgentsServiceVersion.getLatest()) .endpoint(endpoint); - AgentsClient agentsClient = builder.buildAgentsClient(); - ConversationService conversationService = builder.buildOpenAIClient().conversations(); + ConversationService conversations = builder.buildOpenAIClient().conversations(); ResponsesClient responsesClient = builder.buildResponsesClient(); AgentVersionDetails agent = null; + AgentEndpointConfig originalEndpoint = null; String conversationId = null; - try { - // Create a prompt agent - PromptAgentDefinition agentDefinition = new PromptAgentDefinition(model) - .setInstructions("You are a helpful assistant."); - - agent = agentsClient.createAgentVersion("my-agent", new CreateAgentVersionInput(agentDefinition)); - System.out.printf("Agent created (id: %s, version: %s)\n", agent.getId(), agent.getVersion()); + agent = agentsClient.createAgentVersion("basic-conversation-agent", + new CreateAgentVersionInput(new PromptAgentDefinition(model) + .setInstructions("You are a helpful assistant that answers general questions."))); + originalEndpoint = agentsClient.getAgent(agent.getName()).getAgentEndpoint(); - AgentReference agentReference = new AgentReference(agent.getName()) - .setVersion(agent.getVersion()); + AgentEndpointConfig agentEndpoint = new AgentEndpointConfig() + .setVersionSelector(new VersionSelector().setVersionSelectionRules(Collections.singletonList( + new FixedRatioVersionSelectionRule(100).setAgentVersion(agent.getVersion())))) + .setProtocolConfiguration(new ProtocolConfiguration() + .setResponses(new ResponsesProtocolConfiguration())); + agentsClient.updateAgentDetails(agent.getName(), + new UpdateAgentDetailsOptions().setAgentEndpoint(agentEndpoint)); - // Create a conversation - Conversation conversation = conversationService.create(); + Conversation conversation = conversations.create(); conversationId = conversation.id(); - System.out.println("Created conversation: " + conversationId); - - // Create a response using the conversation - Response response = responsesClient.createAzureResponse( - new AzureCreateResponseOptions().setAgentReference(agentReference), + AzureCreateResponseOptions options = new AzureCreateResponseOptions() + .setAgentReference(SampleUtils.toAgentReference(agent)); + Response first = responsesClient.createAzureResponse(options, ResponseCreateParams.builder() .conversation(conversationId) - .input("Hi, how can you help me?")); + .input("What is the size of France in square miles?")); + SampleUtils.printResponseText(first); - // Process and display the response - System.out.println("\n=== Agent Response ==="); - for (ResponseOutputItem outputItem : response.output()) { - if (outputItem.message().isPresent()) { - ResponseOutputMessage message = outputItem.message().get(); - message.content().forEach(content -> { - content.outputText().ifPresent(text -> { - System.out.println("Assistant: " + text.text()); - }); - }); - } - } - System.out.println("Response ID: " + response.id()); - } catch (Exception e) { - System.err.println("Error: " + e.getMessage()); - e.printStackTrace(); + conversations.items().create(ItemCreateParams.builder() + .conversationId(conversationId) + .addItem(EasyInputMessage.builder() + .role(EasyInputMessage.Role.USER) + .content("What is its capital city?") + .build()) + .build()); + Response second = responsesClient.createAzureResponse(options, + ResponseCreateParams.builder().conversation(conversationId)); + SampleUtils.printResponseText(second); } finally { - // Cleanup conversation if (conversationId != null) { - conversationService.delete(conversationId); - System.out.println("Conversation deleted."); + conversations.delete(conversationId); } - // Cleanup agent if (agent != null) { + agentsClient.updateAgentDetails(agent.getName(), + new UpdateAgentDetailsOptions().setAgentEndpoint(originalEndpoint)); agentsClient.deleteAgentVersion(agent.getName(), agent.getVersion()); - System.out.println("Agent deleted."); } } } diff --git a/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/SampleUtils.java b/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/SampleUtils.java index f32f412630040..4ec7636fe6b8c 100644 --- a/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/SampleUtils.java +++ b/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/SampleUtils.java @@ -3,13 +3,64 @@ package com.azure.ai.agents; +import com.azure.ai.agents.models.AgentReference; +import com.azure.ai.agents.models.AgentVersionDetails; +import com.openai.models.responses.Response; +import com.openai.models.responses.ResponseOutputItem; + +import java.io.IOException; +import java.io.UncheckedIOException; import java.net.URISyntaxException; import java.net.URL; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -public class SampleUtils { +public final class SampleUtils { + + private SampleUtils() { + } + + /** + * Creates an agent reference for a version returned by the service. + * + * @param agent the agent version. + * @return the agent reference. + */ + public static AgentReference toAgentReference(AgentVersionDetails agent) { + return new AgentReference(agent.getName()).setVersion(agent.getVersion()); + } + + /** + * Prints all text output in a response. + * + * @param response the response to print. + */ + public static void printResponseText(Response response) { + for (ResponseOutputItem item : response.output()) { + item.message().ifPresent(message -> message.content().forEach(content -> + content.outputText().ifPresent(text -> System.out.println(text.text())))); + } + } + + /** + * Creates a temporary UTF-8 text file. + * + * @param prefix the file prefix. + * @param suffix the file suffix. + * @param content the file content. + * @return the temporary file path. + */ + public static Path createTempFile(String prefix, String suffix, String content) { + try { + Path path = Files.createTempFile(prefix, suffix); + Files.write(path, content.getBytes(StandardCharsets.UTF_8)); + return path; + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } /** * Gets the path to a file in the sample resource folder. diff --git a/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/WorkflowMultiAgentAsyncSample.java b/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/WorkflowMultiAgentAsyncSample.java new file mode 100644 index 0000000000000..41120bb66251e --- /dev/null +++ b/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/WorkflowMultiAgentAsyncSample.java @@ -0,0 +1,78 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.ai.agents; + +import com.azure.ai.agents.models.AgentVersionDetails; +import com.azure.ai.agents.models.AzureCreateResponseOptions; +import com.azure.ai.agents.models.CreateAgentVersionInput; +import com.azure.ai.agents.models.PromptAgentDefinition; +import com.azure.ai.agents.models.WorkflowAgentDefinition; +import com.azure.core.util.Configuration; +import com.azure.identity.DefaultAzureCredentialBuilder; +import com.openai.models.responses.ResponseCreateParams; +import reactor.core.publisher.Mono; + +import java.util.concurrent.atomic.AtomicReference; + +/** + * Demonstrates asynchronously running a workflow that invokes student and teacher prompt agents. + * + *

Before running the sample, set these environment variables:

+ *
    + *
  • {@code FOUNDRY_PROJECT_ENDPOINT} - The Azure AI Project endpoint.
  • + *
  • {@code FOUNDRY_MODEL_NAME} - The model deployment name.
  • + *
+ */ +public class WorkflowMultiAgentAsyncSample { + public static void main(String[] args) { + Configuration configuration = Configuration.getGlobalConfiguration(); + String endpoint = configuration.get("FOUNDRY_PROJECT_ENDPOINT"); + String model = configuration.get("FOUNDRY_MODEL_NAME"); + + AgentsClientBuilder builder = new AgentsClientBuilder() + .credential(new DefaultAzureCredentialBuilder().build()) + .endpoint(endpoint) + .allowPreview(true); + AgentsAsyncClient agentsClient = builder.buildAgentsAsyncClient(); + ResponsesAsyncClient responsesClient = builder.buildResponsesAsyncClient(); + AtomicReference teacherRef = new AtomicReference<>(); + AtomicReference studentRef = new AtomicReference<>(); + AtomicReference workflowRef = new AtomicReference<>(); + + agentsClient.createAgentVersion("teacher-agent-async", + new CreateAgentVersionInput(new PromptAgentDefinition(model) + .setInstructions("Check the student's answer, then explain the correct answer."))) + .doOnNext(teacherRef::set) + .then(agentsClient.createAgentVersion("student-agent-async", + new CreateAgentVersionInput(new PromptAgentDefinition(model) + .setInstructions("Answer the teacher's math question.")))) + .doOnNext(studentRef::set) + .flatMap(student -> agentsClient.createAgentVersion("student-teacher-workflow-async", + new CreateAgentVersionInput(new WorkflowAgentDefinition().setWorkflow( + WorkflowSampleUtils.createStudentTeacherWorkflow(student.getName(), + teacherRef.get().getName()))))) + .doOnNext(workflowRef::set) + .flatMap(workflow -> responsesClient.createAzureResponse( + new AzureCreateResponseOptions().setAgentReference(SampleUtils.toAgentReference(workflow)), + ResponseCreateParams.builder().input("What is 12 multiplied by 8?"))) + .doOnNext(SampleUtils::printResponseText) + .then(cleanup(agentsClient, workflowRef, studentRef, teacherRef)) + .onErrorResume(error -> cleanup(agentsClient, workflowRef, studentRef, teacherRef) + .then(Mono.error(error))) + .block(); + } + + @SafeVarargs + private static Mono cleanup(AgentsAsyncClient client, + AtomicReference... references) { + Mono cleanup = Mono.empty(); + for (AtomicReference reference : references) { + AgentVersionDetails agent = reference.get(); + if (agent != null) { + cleanup = cleanup.then(client.deleteAgentVersion(agent.getName(), agent.getVersion())); + } + } + return cleanup; + } +} diff --git a/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/WorkflowMultiAgentMcpApprovalSample.java b/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/WorkflowMultiAgentMcpApprovalSample.java new file mode 100644 index 0000000000000..d2ed425120011 --- /dev/null +++ b/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/WorkflowMultiAgentMcpApprovalSample.java @@ -0,0 +1,96 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.ai.agents; + +import com.azure.ai.agents.models.AgentVersionDetails; +import com.azure.ai.agents.models.AzureCreateResponseOptions; +import com.azure.ai.agents.models.CreateAgentVersionInput; +import com.azure.ai.agents.models.McpTool; +import com.azure.ai.agents.models.PromptAgentDefinition; +import com.azure.ai.agents.models.WorkflowAgentDefinition; +import com.azure.core.util.Configuration; +import com.azure.identity.DefaultAzureCredentialBuilder; +import com.openai.models.responses.Response; +import com.openai.models.responses.ResponseCreateParams; +import com.openai.models.responses.ResponseInputItem; +import com.openai.models.responses.ResponseOutputItem; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Demonstrates approving MCP calls made by prompt agents within a workflow. + * + *

Before running the sample, set these environment variables:

+ *
    + *
  • {@code FOUNDRY_PROJECT_ENDPOINT} - The Azure AI Project endpoint.
  • + *
  • {@code FOUNDRY_MODEL_NAME} - The model deployment name.
  • + *
+ */ +public class WorkflowMultiAgentMcpApprovalSample { + public static void main(String[] args) { + Configuration configuration = Configuration.getGlobalConfiguration(); + String endpoint = configuration.get("FOUNDRY_PROJECT_ENDPOINT"); + String model = configuration.get("FOUNDRY_MODEL_NAME"); + + AgentsClientBuilder builder = new AgentsClientBuilder() + .credential(new DefaultAzureCredentialBuilder().build()) + .endpoint(endpoint) + .allowPreview(true); + AgentsClient agentsClient = builder.buildAgentsClient(); + ResponsesClient responsesClient = builder.buildResponsesClient(); + AgentVersionDetails teacher = null; + AgentVersionDetails student = null; + AgentVersionDetails workflow = null; + + try { + McpTool mcpTool = new McpTool("api-specs") + .setServerUrl("https://gitmcp.io/Azure/azure-rest-api-specs") + .setRequireApproval("always"); + teacher = createPromptAgent(agentsClient, model, "workflow-teacher-mcp", mcpTool, + "Check the student's answer using the MCP tool."); + student = createPromptAgent(agentsClient, model, "workflow-student-mcp", mcpTool, + "Use the MCP tool to answer the question."); + workflow = agentsClient.createAgentVersion("student-teacher-workflow-mcp", + new CreateAgentVersionInput(new WorkflowAgentDefinition().setWorkflow( + WorkflowSampleUtils.createStudentTeacherWorkflow(student.getName(), teacher.getName())))); + + AzureCreateResponseOptions options = new AzureCreateResponseOptions() + .setAgentReference(SampleUtils.toAgentReference(workflow)); + Response response = responsesClient.createAzureResponse(options, + ResponseCreateParams.builder().input("Summarize the Azure REST API specifications repository.")); + + List approvals = new ArrayList<>(); + for (ResponseOutputItem item : response.output()) { + if (item.isMcpApprovalRequest()) { + approvals.add(ResponseInputItem.ofMcpApprovalResponse( + ResponseInputItem.McpApprovalResponse.builder() + .approvalRequestId(item.asMcpApprovalRequest().id()) + .approve(true) + .build())); + } + } + if (!approvals.isEmpty()) { + response = responsesClient.createAzureResponse(options, + ResponseCreateParams.builder() + .previousResponseId(response.id()) + .inputOfResponse(approvals)); + } + SampleUtils.printResponseText(response); + } finally { + WorkflowMultiAgentSample.deleteVersion(agentsClient, workflow); + WorkflowMultiAgentSample.deleteVersion(agentsClient, student); + WorkflowMultiAgentSample.deleteVersion(agentsClient, teacher); + } + } + + private static AgentVersionDetails createPromptAgent(AgentsClient client, String model, String name, McpTool tool, + String instructions) { + return client.createAgentVersion(name, new CreateAgentVersionInput( + new PromptAgentDefinition(model) + .setInstructions(instructions) + .setTools(Collections.singletonList(tool)))); + } +} diff --git a/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/WorkflowMultiAgentSample.java b/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/WorkflowMultiAgentSample.java new file mode 100644 index 0000000000000..adff34a03c450 --- /dev/null +++ b/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/WorkflowMultiAgentSample.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.AgentVersionDetails; +import com.azure.ai.agents.models.AzureCreateResponseOptions; +import com.azure.ai.agents.models.CreateAgentVersionInput; +import com.azure.ai.agents.models.PromptAgentDefinition; +import com.azure.ai.agents.models.WorkflowAgentDefinition; +import com.azure.core.util.Configuration; +import com.azure.identity.DefaultAzureCredentialBuilder; +import com.openai.models.responses.Response; +import com.openai.models.responses.ResponseCreateParams; + +/** + * Demonstrates a workflow agent that invokes student and teacher prompt agents. + * + *

Before running the sample, set these environment variables:

+ *
    + *
  • {@code FOUNDRY_PROJECT_ENDPOINT} - The Azure AI Project endpoint.
  • + *
  • {@code FOUNDRY_MODEL_NAME} - The model deployment name.
  • + *
+ */ +public class WorkflowMultiAgentSample { + public static void main(String[] args) { + Configuration configuration = Configuration.getGlobalConfiguration(); + String endpoint = configuration.get("FOUNDRY_PROJECT_ENDPOINT"); + String model = configuration.get("FOUNDRY_MODEL_NAME"); + + AgentsClientBuilder builder = new AgentsClientBuilder() + .credential(new DefaultAzureCredentialBuilder().build()) + .endpoint(endpoint) + .allowPreview(true); + AgentsClient agentsClient = builder.buildAgentsClient(); + ResponsesClient responsesClient = builder.buildResponsesClient(); + + AgentVersionDetails teacher = null; + AgentVersionDetails student = null; + AgentVersionDetails workflow = null; + try { + teacher = agentsClient.createAgentVersion("teacher-agent", + new CreateAgentVersionInput(new PromptAgentDefinition(model) + .setInstructions("Check the student's answer, then explain the correct answer."))); + student = agentsClient.createAgentVersion("student-agent", + new CreateAgentVersionInput(new PromptAgentDefinition(model) + .setInstructions("Answer the teacher's math question."))); + workflow = agentsClient.createAgentVersion("student-teacher-workflow", + new CreateAgentVersionInput(new WorkflowAgentDefinition().setWorkflow( + WorkflowSampleUtils.createStudentTeacherWorkflow(student.getName(), teacher.getName())))); + + Response response = responsesClient.createAzureResponse( + new AzureCreateResponseOptions().setAgentReference(SampleUtils.toAgentReference(workflow)), + ResponseCreateParams.builder().input("What is 12 multiplied by 8?")); + SampleUtils.printResponseText(response); + } finally { + deleteVersion(agentsClient, workflow); + deleteVersion(agentsClient, student); + deleteVersion(agentsClient, teacher); + } + } + + static void deleteVersion(AgentsClient client, AgentVersionDetails agent) { + if (agent != null) { + client.deleteAgentVersion(agent.getName(), agent.getVersion()); + } + } +} diff --git a/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/WorkflowSampleUtils.java b/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/WorkflowSampleUtils.java new file mode 100644 index 0000000000000..51c1f5f84942e --- /dev/null +++ b/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/WorkflowSampleUtils.java @@ -0,0 +1,46 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.ai.agents; + +final class WorkflowSampleUtils { + private WorkflowSampleUtils() { + } + + static String createStudentTeacherWorkflow(String studentName, String teacherName) { + return "kind: workflow\n" + + "trigger:\n" + + " kind: OnConversationStart\n" + + " id: student_teacher_workflow\n" + + " actions:\n" + + " - kind: SetVariable\n" + + " id: set_input\n" + + " variable: Local.LatestMessage\n" + + " value: '=UserMessage(System.LastMessageText)'\n" + + " - kind: CreateConversation\n" + + " id: create_student_conversation\n" + + " conversationId: Local.StudentConversationId\n" + + " - kind: CreateConversation\n" + + " id: create_teacher_conversation\n" + + " conversationId: Local.TeacherConversationId\n" + + invokeAction("student", studentName, "Local.StudentConversationId") + + invokeAction("teacher", teacherName, "Local.TeacherConversationId") + + " - kind: SendActivity\n" + + " id: send_teacher_reply\n" + + " activity: '{{Last(Local.LatestMessage).Text}}'\n" + + " - kind: EndConversation\n" + + " id: end_workflow\n"; + } + + private static String invokeAction(String id, String agentName, String conversationId) { + return " - kind: InvokeAzureAgent\n" + + " id: " + id + "\n" + + " conversationId: '=" + conversationId + "'\n" + + " agent:\n" + + " name: " + agentName + "\n" + + " input:\n" + + " messages: '=Local.LatestMessage'\n" + + " output:\n" + + " messages: Local.LatestMessage\n"; + } +} diff --git a/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/agents/GetAgent.java b/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/agents/GetAgent.java index f7dc0a83f6ca7..ce4ee39f1e6d2 100644 --- a/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/agents/GetAgent.java +++ b/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/agents/GetAgent.java @@ -4,25 +4,42 @@ package com.azure.ai.agents.agents; import com.azure.ai.agents.AgentsClient; -import com.azure.ai.agents.AgentsClientBuilder; import com.azure.ai.agents.models.AgentDetails; +import com.azure.ai.agents.models.AgentVersionDetails; +import com.azure.ai.agents.models.CreateAgentVersionInput; +import com.azure.ai.agents.models.PromptAgentDefinition; import com.azure.core.util.Configuration; import com.azure.identity.DefaultAzureCredentialBuilder; +/** + * Demonstrates creating and retrieving an agent. + * + *

Before running the sample, set these environment variables:

+ *
    + *
  • {@code FOUNDRY_PROJECT_ENDPOINT} - The Azure AI Project endpoint.
  • + *
  • {@code FOUNDRY_MODEL_NAME} - The model deployment name.
  • + *
+ */ public class GetAgent { public static void main(String[] args) { - String endpoint = Configuration.getGlobalConfiguration().get("FOUNDRY_PROJECT_ENDPOINT"); - String agentName = "agent_created_from_java"; - // Code sample for creating an agent - AgentsClient agentsClient = new AgentsClientBuilder() - .credential(new DefaultAzureCredentialBuilder().build()) - .endpoint(endpoint) - .buildAgentsClient(); + Configuration configuration = Configuration.getGlobalConfiguration(); + String endpoint = configuration.get("FOUNDRY_PROJECT_ENDPOINT"); + String model = configuration.get("FOUNDRY_MODEL_NAME"); - AgentDetails agent = agentsClient.getAgent(agentName); - - System.out.println("Agent ID: " + agent.getId()); - System.out.println("Agent Name: " + agent.getName()); - System.out.println("Agent Version: " + agent.getVersions().getLatest()); + AgentsClient client = new com.azure.ai.agents.AgentsClientBuilder() + .credential(new DefaultAzureCredentialBuilder().build()) + .endpoint(endpoint) + .buildAgentsClient(); + AgentVersionDetails created = client.createAgentVersion("retrieve-agent-java", + new CreateAgentVersionInput(new PromptAgentDefinition(model) + .setInstructions("You are a helpful assistant."))); + try { + AgentDetails agent = client.getAgent(created.getName()); + System.out.println("Agent ID: " + agent.getId()); + System.out.println("Agent name: " + agent.getName()); + System.out.println("Latest version: " + agent.getVersions().getLatest().getVersion()); + } finally { + client.deleteAgentVersion(created.getName(), created.getVersion()); + } } } diff --git a/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/conversations/GetConversation.java b/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/conversations/GetConversation.java index d50ed31008e48..7322634044d47 100644 --- a/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/conversations/GetConversation.java +++ b/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/conversations/GetConversation.java @@ -9,21 +9,31 @@ import com.openai.models.conversations.Conversation; import com.openai.services.blocking.ConversationService; +/** + * Demonstrates creating and retrieving a conversation. + * + *

Before running the sample, set these environment variables:

+ *
    + *
  • {@code FOUNDRY_PROJECT_ENDPOINT} - The Azure AI Project endpoint.
  • + *
+ */ public class GetConversation { public static void main(String[] args) { - String endpoint = Configuration.getGlobalConfiguration().get("FOUNDRY_PROJECT_ENDPOINT"); - String conversationId = "your-conversation-id"; // Replace with actual conversation ID - // Code sample for retrieving a conversation - ConversationService conversationService = new AgentsClientBuilder() - .credential(new DefaultAzureCredentialBuilder().build()) - .endpoint(endpoint) - .buildOpenAIClient() - .conversations(); + Configuration configuration = Configuration.getGlobalConfiguration(); + String endpoint = configuration.get("FOUNDRY_PROJECT_ENDPOINT"); - Conversation conversation = conversationService.retrieve(conversationId); - - System.out.println("Conversation ID: " + conversation.id()); - System.out.println("Conversation Created At: " + conversation.createdAt()); - System.out.println("Conversation Metadata: " + conversation._metadata()); + AgentsClientBuilder builder = new AgentsClientBuilder() + .credential(new DefaultAzureCredentialBuilder().build()) + .endpoint(endpoint); + ConversationService conversations = builder.buildOpenAIClient().conversations(); + Conversation created = conversations.create(); + try { + Conversation retrieved = conversations.retrieve(created.id()); + System.out.println("Conversation ID: " + retrieved.id()); + System.out.println("Created at: " + retrieved.createdAt()); + System.out.println("Metadata: " + retrieved._metadata()); + } finally { + conversations.delete(created.id()); + } } } diff --git a/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/optimization/AgentOptimizationAdvancedPollingAsyncSample.java b/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/optimization/AgentOptimizationAdvancedPollingAsyncSample.java new file mode 100644 index 0000000000000..0ab8ef6cb9c31 --- /dev/null +++ b/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/optimization/AgentOptimizationAdvancedPollingAsyncSample.java @@ -0,0 +1,79 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.ai.agents.optimization; + +import com.azure.ai.agents.BetaAgentsAsyncClient; +import com.azure.ai.agents.AgentsClientBuilder; +import com.azure.core.util.Configuration; +import com.azure.identity.DefaultAzureCredentialBuilder; +import com.azure.ai.agents.models.AgentOptimizationJob; +import reactor.core.publisher.Mono; + +import java.time.Duration; +import java.util.concurrent.atomic.AtomicReference; + +/** + * Demonstrates asynchronous application-managed polling of an agent optimization job. + * + *

Before running the sample, set these environment variables:

+ *
    + *
  • {@code FOUNDRY_PROJECT_ENDPOINT} - The Azure AI Project endpoint.
  • + *
  • {@code FOUNDRY_AGENT_NAME} - The registered agent to optimize.
  • + *
  • {@code DATASET_NAME} - The registered training dataset.
  • + *
  • {@code DATASET_VERSION} - Optional. The training dataset version. Defaults to {@code 1}.
  • + *
  • {@code EVALUATOR_NAME} - Optional. The registered evaluator name. Defaults to {@code task_adherence}.
  • + *
  • {@code MAX_CANDIDATES} - Optional. The maximum number of optimization candidates. Defaults to {@code 2}.
  • + *
  • {@code EVAL_MODEL} - Optional. The model deployment used to evaluate candidates. Defaults to {@code gpt-4.1-mini}.
  • + *
  • {@code OPTIMIZATION_MODEL} - Optional. The model deployment used to generate candidates. Defaults to {@code gpt-5.1}.
  • + *
+ */ +public class AgentOptimizationAdvancedPollingAsyncSample { + public static void main(String[] args) { + Configuration configuration = Configuration.getGlobalConfiguration(); + String endpoint = configuration.get("FOUNDRY_PROJECT_ENDPOINT"); + String agentName = configuration.get("FOUNDRY_AGENT_NAME"); + String datasetName = configuration.get("DATASET_NAME"); + String datasetVersion = configuration.get("DATASET_VERSION", "1"); + String evaluatorName = configuration.get("EVALUATOR_NAME", "task_adherence"); + int maxCandidates = Integer.parseInt(configuration.get("MAX_CANDIDATES", "2")); + String evalModel = configuration.get("EVAL_MODEL", "gpt-4.1-mini"); + String optimizationModel = configuration.get("OPTIMIZATION_MODEL", "gpt-5.1"); + + AgentsClientBuilder builder = new AgentsClientBuilder() + .credential(new DefaultAzureCredentialBuilder().build()) + .endpoint(endpoint); + BetaAgentsAsyncClient client = builder.beta().buildBetaAgentsAsyncClient(); + AtomicReference jobIdRef = new AtomicReference<>(); + + client.beginCreateOptimizationJob(AgentOptimizationSampleUtils.createJob(agentName, datasetName, datasetVersion, evaluatorName, + maxCandidates, evalModel, optimizationModel)) + .next() + .flatMap(response -> { + AgentOptimizationJob job = response.getValue(); + if (job == null || job.getId() == null) { + return Mono.error(new IllegalStateException("The service did not return an optimization job ID.")); + } + jobIdRef.set(job.getId()); + return poll(client, job); + }) + .doOnNext(job -> System.out.printf("Job %s completed with status: %s%n", + job.getId(), job.getStatus())) + .then(Mono.defer(() -> jobIdRef.get() == null + ? Mono.empty() : client.deleteOptimizationJob(jobIdRef.get()))) + .onErrorResume(error -> Mono.defer(() -> jobIdRef.get() == null + ? Mono.empty() : client.deleteOptimizationJob(jobIdRef.get())) + .then(Mono.error(error))) + .block(); + } + + private static Mono poll(BetaAgentsAsyncClient client, AgentOptimizationJob job) { + System.out.printf("Job %s status: %s%n", job.getId(), job.getStatus()); + if (AgentOptimizationSampleUtils.isTerminal(job.getStatus())) { + return Mono.just(job); + } + return Mono.delay(Duration.ofSeconds(10)) + .then(client.getOptimizationJob(job.getId())) + .flatMap(updated -> poll(client, updated)); + } +} diff --git a/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/optimization/AgentOptimizationAdvancedPollingSample.java b/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/optimization/AgentOptimizationAdvancedPollingSample.java new file mode 100644 index 0000000000000..aeb3e3fbd0bd6 --- /dev/null +++ b/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/optimization/AgentOptimizationAdvancedPollingSample.java @@ -0,0 +1,68 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.ai.agents.optimization; + +import com.azure.ai.agents.BetaAgentsClient; +import com.azure.ai.agents.AgentsClientBuilder; +import com.azure.core.util.Configuration; +import com.azure.identity.DefaultAzureCredentialBuilder; +import com.azure.ai.agents.models.AgentOptimizationJob; +import com.azure.core.util.polling.PollResponse; +import com.azure.core.util.polling.SyncPoller; + +import java.time.Duration; + +/** + * Demonstrates application-managed polling of an agent optimization job. + * + *

Before running the sample, set these environment variables:

+ *
    + *
  • {@code FOUNDRY_PROJECT_ENDPOINT} - The Azure AI Project endpoint.
  • + *
  • {@code FOUNDRY_AGENT_NAME} - The registered agent to optimize.
  • + *
  • {@code DATASET_NAME} - The registered training dataset.
  • + *
  • {@code DATASET_VERSION} - Optional. The training dataset version. Defaults to {@code 1}.
  • + *
  • {@code EVALUATOR_NAME} - Optional. The registered evaluator name. Defaults to {@code task_adherence}.
  • + *
  • {@code MAX_CANDIDATES} - Optional. The maximum number of optimization candidates. Defaults to {@code 2}.
  • + *
  • {@code EVAL_MODEL} - Optional. The model deployment used to evaluate candidates. Defaults to {@code gpt-4.1-mini}.
  • + *
  • {@code OPTIMIZATION_MODEL} - Optional. The model deployment used to generate candidates. Defaults to {@code gpt-5.1}.
  • + *
+ */ +public class AgentOptimizationAdvancedPollingSample { + public static void main(String[] args) throws InterruptedException { + Configuration configuration = Configuration.getGlobalConfiguration(); + String endpoint = configuration.get("FOUNDRY_PROJECT_ENDPOINT"); + String agentName = configuration.get("FOUNDRY_AGENT_NAME"); + String datasetName = configuration.get("DATASET_NAME"); + String datasetVersion = configuration.get("DATASET_VERSION", "1"); + String evaluatorName = configuration.get("EVALUATOR_NAME", "task_adherence"); + int maxCandidates = Integer.parseInt(configuration.get("MAX_CANDIDATES", "2")); + String evalModel = configuration.get("EVAL_MODEL", "gpt-4.1-mini"); + String optimizationModel = configuration.get("OPTIMIZATION_MODEL", "gpt-5.1"); + + AgentsClientBuilder builder = new AgentsClientBuilder() + .credential(new DefaultAzureCredentialBuilder().build()) + .endpoint(endpoint); + BetaAgentsClient client = builder.beta().buildBetaAgentsClient(); + SyncPoller poller = client.beginCreateOptimizationJob( + AgentOptimizationSampleUtils.createJob(agentName, datasetName, datasetVersion, evaluatorName, + maxCandidates, evalModel, optimizationModel)); + PollResponse initial = poller.poll(); + AgentOptimizationJob job = initial.getValue(); + if (job == null || job.getId() == null) { + throw new IllegalStateException("The service did not return an optimization job ID."); + } + + String jobId = job.getId(); + try { + while (!AgentOptimizationSampleUtils.isTerminal(job.getStatus())) { + System.out.printf("Job %s status: %s%n", jobId, job.getStatus()); + Thread.sleep(Duration.ofSeconds(10).toMillis()); + job = client.getOptimizationJob(jobId); + } + System.out.printf("Job %s completed with status: %s%n", jobId, job.getStatus()); + } finally { + client.deleteOptimizationJob(jobId); + } + } +} diff --git a/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/optimization/AgentOptimizationCancelSample.java b/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/optimization/AgentOptimizationCancelSample.java new file mode 100644 index 0000000000000..c38ee478e866c --- /dev/null +++ b/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/optimization/AgentOptimizationCancelSample.java @@ -0,0 +1,62 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.ai.agents.optimization; + +import com.azure.ai.agents.BetaAgentsClient; +import com.azure.ai.agents.AgentsClientBuilder; +import com.azure.core.util.Configuration; +import com.azure.identity.DefaultAzureCredentialBuilder; +import com.azure.ai.agents.models.AgentOptimizationJob; +import com.azure.core.util.polling.PollResponse; +import com.azure.core.util.polling.SyncPoller; + +/** + * Demonstrates cancelling an in-progress agent optimization job. + * + *

Before running the sample, set these environment variables:

+ *
    + *
  • {@code FOUNDRY_PROJECT_ENDPOINT} - The Azure AI Project endpoint.
  • + *
  • {@code FOUNDRY_AGENT_NAME} - The registered agent to optimize.
  • + *
  • {@code DATASET_NAME} - The registered training dataset.
  • + *
  • {@code DATASET_VERSION} - Optional. The training dataset version. Defaults to {@code 1}.
  • + *
  • {@code EVALUATOR_NAME} - Optional. The registered evaluator name. Defaults to {@code task_adherence}.
  • + *
  • {@code MAX_CANDIDATES} - Optional. The maximum number of optimization candidates. Defaults to {@code 2}.
  • + *
  • {@code EVAL_MODEL} - Optional. The model deployment used to evaluate candidates. Defaults to {@code gpt-4.1-mini}.
  • + *
  • {@code OPTIMIZATION_MODEL} - Optional. The model deployment used to generate candidates. Defaults to {@code gpt-5.1}.
  • + *
+ */ +public class AgentOptimizationCancelSample { + public static void main(String[] args) { + Configuration configuration = Configuration.getGlobalConfiguration(); + String endpoint = configuration.get("FOUNDRY_PROJECT_ENDPOINT"); + String agentName = configuration.get("FOUNDRY_AGENT_NAME"); + String datasetName = configuration.get("DATASET_NAME"); + String datasetVersion = configuration.get("DATASET_VERSION", "1"); + String evaluatorName = configuration.get("EVALUATOR_NAME", "task_adherence"); + int maxCandidates = Integer.parseInt(configuration.get("MAX_CANDIDATES", "2")); + String evalModel = configuration.get("EVAL_MODEL", "gpt-4.1-mini"); + String optimizationModel = configuration.get("OPTIMIZATION_MODEL", "gpt-5.1"); + + AgentsClientBuilder builder = new AgentsClientBuilder() + .credential(new DefaultAzureCredentialBuilder().build()) + .endpoint(endpoint); + BetaAgentsClient client = builder.beta().buildBetaAgentsClient(); + SyncPoller poller = client.beginCreateOptimizationJob( + AgentOptimizationSampleUtils.createJob(agentName, datasetName, datasetVersion, evaluatorName, + maxCandidates, evalModel, optimizationModel)); + PollResponse initial = poller.poll(); + AgentOptimizationJob job = initial.getValue(); + if (job == null || job.getId() == null) { + throw new IllegalStateException("The service did not return an optimization job ID."); + } + + try { + AgentOptimizationJob cancelled = client.cancelOptimizationJob(job.getId()); + System.out.printf("Cancellation requested for %s; status is %s%n", + cancelled.getId(), cancelled.getStatus()); + } finally { + client.deleteOptimizationJob(job.getId()); + } + } +} diff --git a/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/optimization/AgentOptimizationListGetDeleteSample.java b/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/optimization/AgentOptimizationListGetDeleteSample.java new file mode 100644 index 0000000000000..dcb1e57361e29 --- /dev/null +++ b/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/optimization/AgentOptimizationListGetDeleteSample.java @@ -0,0 +1,59 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.ai.agents.optimization; + +import com.azure.ai.agents.BetaAgentsClient; +import com.azure.ai.agents.AgentsClientBuilder; +import com.azure.core.util.Configuration; +import com.azure.identity.DefaultAzureCredentialBuilder; +import com.azure.ai.agents.models.AgentOptimizationJob; +import com.azure.ai.agents.models.AgentOptimizationJobListItem; +import com.azure.ai.agents.models.PageOrder; + +/** + * Demonstrates listing, retrieving, and optionally deleting agent optimization jobs. + * + *

Before running the sample, set these environment variables:

+ *
    + *
  • {@code FOUNDRY_PROJECT_ENDPOINT} - The Azure AI Project endpoint.
  • + *
  • {@code OPTIMIZATION_JOB_ID} - Optional. The optimization job to retrieve; defaults to the first listed job.
  • + *
  • {@code DELETE_OPTIMIZATION_JOB} - Optional. Whether to delete the retrieved optimization job. Defaults to {@code false}.
  • + *
+ */ +public class AgentOptimizationListGetDeleteSample { + public static void main(String[] args) { + Configuration configuration = Configuration.getGlobalConfiguration(); + String endpoint = configuration.get("FOUNDRY_PROJECT_ENDPOINT"); + String configuredJobId = configuration.get("OPTIMIZATION_JOB_ID"); + boolean deleteJob = Boolean.parseBoolean(configuration.get("DELETE_OPTIMIZATION_JOB", "false")); + + BetaAgentsClient client = new AgentsClientBuilder() + .credential(new DefaultAzureCredentialBuilder().build()) + .endpoint(endpoint) + .beta() + .buildBetaAgentsClient(); + String firstJobId = null; + + for (AgentOptimizationJobListItem item : client.listOptimizationJobs(10, PageOrder.DESC, + null, null, null, null)) { + System.out.printf("Job %s: %s%n", item.getId(), item.getStatus()); + if (firstJobId == null) { + firstJobId = item.getId(); + } + } + + String jobId = configuredJobId == null ? firstJobId : configuredJobId; + if (jobId == null) { + System.out.println("No optimization jobs were found."); + return; + } + + AgentOptimizationJob job = client.getOptimizationJob(jobId); + System.out.printf("Retrieved job %s: %s%n", job.getId(), job.getStatus()); + if (deleteJob) { + client.deleteOptimizationJob(jobId); + System.out.println("Deleted job: " + jobId); + } + } +} diff --git a/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/optimization/AgentOptimizationSampleUtils.java b/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/optimization/AgentOptimizationSampleUtils.java new file mode 100644 index 0000000000000..7571f9fc8d868 --- /dev/null +++ b/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/optimization/AgentOptimizationSampleUtils.java @@ -0,0 +1,40 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.ai.agents.optimization; + +import com.azure.ai.agents.models.AgentOptimizationEvaluatorRef; +import com.azure.ai.agents.models.AgentOptimizationJob; +import com.azure.ai.agents.models.AgentOptimizationJobInputs; +import com.azure.ai.agents.models.AgentOptimizationOptions; +import com.azure.ai.agents.models.AgentOptimizationReferenceDatasetInput; +import com.azure.ai.agents.models.JobStatus; +import com.azure.ai.agents.models.OptimizedAgentIdentifier; + +import java.util.Collections; + +final class AgentOptimizationSampleUtils { + private AgentOptimizationSampleUtils() { + } + + static AgentOptimizationJob createJob(String agentName, String datasetName, String datasetVersion, + String evaluatorName, int maxCandidates, String evalModel, String optimizationModel) { + AgentOptimizationEvaluatorRef evaluator = new AgentOptimizationEvaluatorRef(evaluatorName); + AgentOptimizationReferenceDatasetInput dataset = new AgentOptimizationReferenceDatasetInput(datasetName) + .setVersion(datasetVersion); + AgentOptimizationOptions options = new AgentOptimizationOptions() + .setMaxCandidates(maxCandidates) + .setEvalModel(evalModel) + .setOptimizationModel(optimizationModel); + AgentOptimizationJobInputs inputs = new AgentOptimizationJobInputs( + new OptimizedAgentIdentifier(agentName), dataset, Collections.singletonList(evaluator)) + .setOptions(options); + return new AgentOptimizationJob().setInputs(inputs); + } + + static boolean isTerminal(JobStatus status) { + return JobStatus.SUCCEEDED.equals(status) + || JobStatus.FAILED.equals(status) + || JobStatus.CANCELLED.equals(status); + } +} diff --git a/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/telemetry/AgentAzureMonitorTracingSample.java b/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/telemetry/AgentAzureMonitorTracingSample.java new file mode 100644 index 0000000000000..626aa09ef11e9 --- /dev/null +++ b/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/telemetry/AgentAzureMonitorTracingSample.java @@ -0,0 +1,47 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.ai.agents.telemetry; + +import com.azure.ai.agents.AgentsClientBuilder; +import com.azure.core.util.Configuration; +import com.azure.identity.DefaultAzureCredentialBuilder; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.context.Scope; +import io.opentelemetry.sdk.OpenTelemetrySdk; + +/** + * Demonstrates exporting agent traces to Azure Monitor. + * + *

Before running the sample, set these environment variables:

+ *
    + *
  • {@code FOUNDRY_PROJECT_ENDPOINT} - The Azure AI Project endpoint.
  • + *
  • {@code FOUNDRY_MODEL_NAME} - The model deployment name.
  • + *
  • {@code APPLICATIONINSIGHTS_CONNECTION_STRING} - The Azure Monitor Application Insights connection string.
  • + *
+ */ +public class AgentAzureMonitorTracingSample { + @SuppressWarnings("try") + public static void main(String[] args) { + Configuration configuration = Configuration.getGlobalConfiguration(); + String endpoint = configuration.get("FOUNDRY_PROJECT_ENDPOINT"); + String model = configuration.get("FOUNDRY_MODEL_NAME"); + String connectionString = configuration.get("APPLICATIONINSIGHTS_CONNECTION_STRING"); + AgentsClientBuilder builder = new AgentsClientBuilder() + .credential(new DefaultAzureCredentialBuilder().build()) + .endpoint(endpoint); + try (OpenTelemetrySdk telemetry + = AgentTelemetrySampleUtils.configureAzureMonitorTelemetry(connectionString)) { + Span span = telemetry.getTracer(AgentAzureMonitorTracingSample.class.getName()) + .spanBuilder("run-agent").startSpan(); + try (Scope ignored = span.makeCurrent()) { + AgentTelemetrySampleUtils.runAgent(builder, model); + } catch (RuntimeException error) { + span.recordException(error); + throw error; + } finally { + span.end(); + } + } + } +} diff --git a/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/telemetry/AgentConsoleTracingCustomAttributesSample.java b/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/telemetry/AgentConsoleTracingCustomAttributesSample.java new file mode 100644 index 0000000000000..44c4af59a4aa9 --- /dev/null +++ b/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/telemetry/AgentConsoleTracingCustomAttributesSample.java @@ -0,0 +1,49 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.ai.agents.telemetry; + +import com.azure.ai.agents.AgentsClientBuilder; +import com.azure.core.util.Configuration; +import com.azure.identity.DefaultAzureCredentialBuilder; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.context.Scope; +import io.opentelemetry.sdk.OpenTelemetrySdk; + +/** + * Demonstrates adding application-specific attributes to an agent trace. + * + *

Before running the sample, set these environment variables:

+ *
    + *
  • {@code FOUNDRY_PROJECT_ENDPOINT} - The Azure AI Project endpoint.
  • + *
  • {@code FOUNDRY_MODEL_NAME} - The model deployment name.
  • + *
+ */ +public class AgentConsoleTracingCustomAttributesSample { + @SuppressWarnings("try") + public static void main(String[] args) { + Configuration configuration = Configuration.getGlobalConfiguration(); + String endpoint = configuration.get("FOUNDRY_PROJECT_ENDPOINT"); + String model = configuration.get("FOUNDRY_MODEL_NAME"); + AgentsClientBuilder builder = new AgentsClientBuilder() + .credential(new DefaultAzureCredentialBuilder().build()) + .endpoint(endpoint); + + try (OpenTelemetrySdk telemetry = AgentTelemetrySampleUtils.configureConsoleTelemetry()) { + Span span = telemetry.getTracer(AgentConsoleTracingCustomAttributesSample.class.getName()) + .spanBuilder("run-agent") + .setAttribute("sample.scenario", "custom-attributes") + .setAttribute("sample.agent.type", "prompt") + .startSpan(); + try (Scope ignored = span.makeCurrent()) { + AgentTelemetrySampleUtils.runAgent(builder, model); + span.setAttribute("sample.completed", true); + } catch (RuntimeException error) { + span.recordException(error); + throw error; + } finally { + span.end(); + } + } + } +} diff --git a/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/telemetry/AgentConsoleTracingSample.java b/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/telemetry/AgentConsoleTracingSample.java new file mode 100644 index 0000000000000..ccd709a7ef92f --- /dev/null +++ b/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/telemetry/AgentConsoleTracingSample.java @@ -0,0 +1,45 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.ai.agents.telemetry; + +import com.azure.ai.agents.AgentsClientBuilder; +import com.azure.core.util.Configuration; +import com.azure.identity.DefaultAzureCredentialBuilder; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.context.Scope; +import io.opentelemetry.sdk.OpenTelemetrySdk; + +/** + * Demonstrates exporting agent traces to the console. + * + *

Before running the sample, set these environment variables:

+ *
    + *
  • {@code FOUNDRY_PROJECT_ENDPOINT} - The Azure AI Project endpoint.
  • + *
  • {@code FOUNDRY_MODEL_NAME} - The model deployment name.
  • + *
+ */ +public class AgentConsoleTracingSample { + @SuppressWarnings("try") + public static void main(String[] args) { + Configuration configuration = Configuration.getGlobalConfiguration(); + String endpoint = configuration.get("FOUNDRY_PROJECT_ENDPOINT"); + String model = configuration.get("FOUNDRY_MODEL_NAME"); + AgentsClientBuilder builder = new AgentsClientBuilder() + .credential(new DefaultAzureCredentialBuilder().build()) + .endpoint(endpoint); + + try (OpenTelemetrySdk telemetry = AgentTelemetrySampleUtils.configureConsoleTelemetry()) { + Span span = telemetry.getTracer(AgentConsoleTracingSample.class.getName()) + .spanBuilder("run-agent").startSpan(); + try (Scope ignored = span.makeCurrent()) { + AgentTelemetrySampleUtils.runAgent(builder, model); + } catch (RuntimeException error) { + span.recordException(error); + throw error; + } finally { + span.end(); + } + } + } +} diff --git a/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/telemetry/AgentTelemetrySampleUtils.java b/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/telemetry/AgentTelemetrySampleUtils.java new file mode 100644 index 0000000000000..f82601023c32b --- /dev/null +++ b/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/telemetry/AgentTelemetrySampleUtils.java @@ -0,0 +1,57 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.ai.agents.telemetry; + +import com.azure.ai.agents.AgentsClient; +import com.azure.ai.agents.AgentsClientBuilder; +import com.azure.ai.agents.ResponsesClient; +import com.azure.ai.agents.SampleUtils; +import com.azure.ai.agents.models.AgentVersionDetails; +import com.azure.ai.agents.models.AzureCreateResponseOptions; +import com.azure.ai.agents.models.CreateAgentVersionInput; +import com.azure.ai.agents.models.PromptAgentDefinition; +import com.azure.monitor.opentelemetry.autoconfigure.AzureMonitorAutoConfigure; +import com.openai.models.responses.Response; +import com.openai.models.responses.ResponseCreateParams; +import io.opentelemetry.exporter.logging.LoggingSpanExporter; +import io.opentelemetry.sdk.OpenTelemetrySdk; +import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk; +import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdkBuilder; +import io.opentelemetry.sdk.trace.SdkTracerProvider; +import io.opentelemetry.sdk.trace.export.SimpleSpanProcessor; + +final class AgentTelemetrySampleUtils { + private AgentTelemetrySampleUtils() { + } + + static OpenTelemetrySdk configureConsoleTelemetry() { + SdkTracerProvider provider = SdkTracerProvider.builder() + .addSpanProcessor(SimpleSpanProcessor.create(LoggingSpanExporter.create())) + .build(); + return OpenTelemetrySdk.builder().setTracerProvider(provider).buildAndRegisterGlobal(); + } + + static OpenTelemetrySdk configureAzureMonitorTelemetry(String connectionString) { + AutoConfiguredOpenTelemetrySdkBuilder builder = AutoConfiguredOpenTelemetrySdk.builder(); + AzureMonitorAutoConfigure.customize(builder, connectionString); + return builder.setResultAsGlobal().build().getOpenTelemetrySdk(); + } + + static Response runAgent(AgentsClientBuilder builder, String model) { + AgentsClient agentsClient = builder.buildAgentsClient(); + ResponsesClient responsesClient = builder.buildResponsesClient(); + AgentVersionDetails agent = agentsClient.createAgentVersion("telemetry-sample-agent", + new CreateAgentVersionInput(new PromptAgentDefinition(model) + .setInstructions("Answer general questions concisely."))); + try { + Response response = responsesClient.createAzureResponse( + new AzureCreateResponseOptions().setAgentReference(SampleUtils.toAgentReference(agent)), + ResponseCreateParams.builder().input("What is the capital of France?")); + SampleUtils.printResponseText(response); + return response; + } finally { + agentsClient.deleteAgentVersion(agent.getName(), agent.getVersion()); + } + } +} diff --git a/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/toolboxes/ToolboxSearchAgentAsyncSample.java b/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/toolboxes/ToolboxSearchAgentAsyncSample.java new file mode 100644 index 0000000000000..aeeeedc0f3f32 --- /dev/null +++ b/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/toolboxes/ToolboxSearchAgentAsyncSample.java @@ -0,0 +1,98 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.ai.agents.toolboxes; + +import com.azure.ai.agents.AgentsAsyncClient; +import com.azure.ai.agents.AgentsClientBuilder; +import com.azure.ai.agents.ResponsesAsyncClient; +import com.azure.ai.agents.ToolboxesAsyncClient; +import com.azure.ai.agents.models.AgentReference; +import com.azure.ai.agents.models.AgentVersionDetails; +import com.azure.ai.agents.models.AzureCreateResponseOptions; +import com.azure.ai.agents.models.CreateAgentVersionInput; +import com.azure.ai.agents.models.McpTool; +import com.azure.ai.agents.models.McpToolboxTool; +import com.azure.ai.agents.models.PromptAgentDefinition; +import com.azure.ai.agents.models.ToolSearchToolboxTool; +import com.azure.ai.agents.models.ToolboxTool; +import com.azure.core.credential.TokenCredential; +import com.azure.core.credential.TokenRequestContext; +import com.azure.core.exception.ResourceNotFoundException; +import com.azure.core.util.BinaryData; +import com.azure.core.util.Configuration; +import com.azure.identity.DefaultAzureCredentialBuilder; +import com.openai.models.responses.ResponseCreateParams; +import reactor.core.publisher.Mono; + +import java.util.Arrays; +import java.util.Collections; +import java.util.concurrent.atomic.AtomicReference; + +/** + * Demonstrates asynchronously creating a tool-search toolbox and invoking its MCP endpoint. + * + *

Before running the sample, set these environment variables:

+ *
    + *
  • {@code FOUNDRY_PROJECT_ENDPOINT} - The Azure AI Project endpoint.
  • + *
  • {@code MCP_PROJECT_CONNECTION_ID} - The project connection resource ID used by the inner MCP server.
  • + *
  • {@code FOUNDRY_MODEL_NAME} - The model deployment name.
  • + *
+ */ +public class ToolboxSearchAgentAsyncSample { + public static void main(String[] args) { + Configuration configuration = Configuration.getGlobalConfiguration(); + String endpoint = configuration.get("FOUNDRY_PROJECT_ENDPOINT"); + String toolboxName = "toolbox-search-async-java"; + TokenCredential credential = new DefaultAzureCredentialBuilder().build(); + AgentsClientBuilder builder = new AgentsClientBuilder().credential(credential).endpoint(endpoint); + ToolboxesAsyncClient toolboxesClient = builder.buildToolboxesAsyncClient(); + AgentsAsyncClient agentsClient = builder.buildAgentsAsyncClient(); + ResponsesAsyncClient responsesClient = builder.buildResponsesAsyncClient(); + AtomicReference agentRef = new AtomicReference<>(); + + McpToolboxTool innerMcp = new McpToolboxTool("github") + .setServerUrl("https://api.githubcopilot.com/mcp") + .setProjectConnectionId(configuration.get("MCP_PROJECT_CONNECTION_ID")) + .setRequireApproval(BinaryData.fromString("\"never\"")) + .setDeferLoading(true); + ToolSearchToolboxTool search = new ToolSearchToolboxTool(); + + toolboxesClient.deleteToolbox(toolboxName) + .onErrorResume(ResourceNotFoundException.class, ignored -> Mono.empty()) + .then(toolboxesClient.createToolboxVersion(toolboxName, + Arrays.asList(innerMcp, search), "Tool-search toolbox", null, null, null)) + .zipWith(credential.getToken(new TokenRequestContext().addScopes("https://ai.azure.com/.default"))) + .flatMap(tuple -> { + String toolboxUrl = endpoint + "/toolboxes/" + toolboxName + "/versions/" + + tuple.getT1().getVersion() + "/mcp?api-version=v1"; + McpTool toolboxMcp = new McpTool("search-tool") + .setServerUrl(toolboxUrl) + .setAuthorization(tuple.getT2().getToken()) + .setRequireApproval("never"); + return agentsClient.createAgentVersion("toolbox-search-agent-async", + new CreateAgentVersionInput(new PromptAgentDefinition(configuration.get("FOUNDRY_MODEL_NAME")) + .setInstructions("Use tool_search to discover a tool, then call_tool to invoke it.") + .setTools(Collections.singletonList(toolboxMcp)))); + }) + .doOnNext(agentRef::set) + .flatMap(agent -> responsesClient.createAzureResponse( + new AzureCreateResponseOptions().setAgentReference( + new AgentReference(agent.getName()).setVersion(agent.getVersion())), + ResponseCreateParams.builder().input("What is my GitHub profile username?"))) + .doOnNext(response -> System.out.println("Response: " + response.output())) + .then(cleanup(agentsClient, toolboxesClient, toolboxName, agentRef)) + .onErrorResume(error -> cleanup(agentsClient, toolboxesClient, toolboxName, agentRef) + .then(Mono.error(error))) + .block(); + } + + private static Mono cleanup(AgentsAsyncClient agentsClient, ToolboxesAsyncClient toolboxesClient, + String toolboxName, AtomicReference agentRef) { + AgentVersionDetails agent = agentRef.get(); + Mono deleteAgent = agent == null ? Mono.empty() + : agentsClient.deleteAgentVersion(agent.getName(), agent.getVersion()); + return deleteAgent.then(toolboxesClient.deleteToolbox(toolboxName) + .onErrorResume(ResourceNotFoundException.class, ignored -> Mono.empty())); + } +} diff --git a/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/toolboxes/ToolboxSearchToolboxSample.java b/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/toolboxes/ToolboxSearchToolboxSample.java index 12232aadaa431..69a41d38aa70c 100644 --- a/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/toolboxes/ToolboxSearchToolboxSample.java +++ b/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/toolboxes/ToolboxSearchToolboxSample.java @@ -3,68 +3,96 @@ package com.azure.ai.agents.toolboxes; +import com.azure.ai.agents.AgentsClient; import com.azure.ai.agents.AgentsClientBuilder; +import com.azure.ai.agents.ResponsesClient; import com.azure.ai.agents.ToolboxesClient; +import com.azure.ai.agents.models.AgentVersionDetails; +import com.azure.ai.agents.models.AzureCreateResponseOptions; +import com.azure.ai.agents.models.CreateAgentVersionInput; +import com.azure.ai.agents.models.McpTool; +import com.azure.ai.agents.models.McpToolboxTool; +import com.azure.ai.agents.models.PromptAgentDefinition; import com.azure.ai.agents.models.ToolSearchToolboxTool; import com.azure.ai.agents.models.ToolboxTool; import com.azure.ai.agents.models.ToolboxVersionDetails; +import com.azure.core.credential.TokenCredential; +import com.azure.core.credential.TokenRequestContext; import com.azure.core.exception.ResourceNotFoundException; +import com.azure.core.util.BinaryData; 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.Collections; +import java.util.Arrays; /** - * This sample demonstrates creating a toolbox version that includes the Toolbox Search tool. + * Demonstrates creating a tool-search toolbox and invoking its MCP endpoint from a prompt agent. * - *

Toolboxes are a preview feature. Before running, set {@code FOUNDRY_PROJECT_ENDPOINT} to your Azure AI Foundry - * project endpoint.

+ *

Before running the sample, set these environment variables:

+ *
    + *
  • {@code FOUNDRY_PROJECT_ENDPOINT} - The Azure AI Project endpoint.
  • + *
  • {@code MCP_PROJECT_CONNECTION_ID} - The project connection resource ID used by the inner MCP server.
  • + *
  • {@code FOUNDRY_MODEL_NAME} - The model deployment name.
  • + *
*/ public class ToolboxSearchToolboxSample { public static void main(String[] args) { - String endpoint = Configuration.getGlobalConfiguration().get("FOUNDRY_PROJECT_ENDPOINT"); - String toolboxName = "toolbox-search-tool-java"; - - ToolboxesClient toolboxesClient = new AgentsClientBuilder() - .credential(new DefaultAzureCredentialBuilder().build()) - .endpoint(endpoint) - .buildToolboxesClient(); - - try { - toolboxesClient.deleteToolbox(toolboxName); - } catch (ResourceNotFoundException ignored) { - // The sample toolbox does not already exist. - } + Configuration configuration = Configuration.getGlobalConfiguration(); + String endpoint = configuration.get("FOUNDRY_PROJECT_ENDPOINT"); + String toolboxName = "toolbox-search-java"; + TokenCredential credential = new DefaultAzureCredentialBuilder().build(); + AgentsClientBuilder builder = new AgentsClientBuilder().credential(credential).endpoint(endpoint); + ToolboxesClient toolboxesClient = builder.buildToolboxesClient(); + AgentsClient agentsClient = builder.buildAgentsClient(); + ResponsesClient responsesClient = builder.buildResponsesClient(); + AgentVersionDetails agent = null; + deleteToolboxIfPresent(toolboxesClient, toolboxName); try { // BEGIN: com.azure.ai.agents.toolboxes.ToolboxSearchToolboxSample.createToolboxSearchToolbox + McpToolboxTool innerMcp = new McpToolboxTool("github") + .setServerUrl("https://api.githubcopilot.com/mcp") + .setProjectConnectionId(configuration.get("MCP_PROJECT_CONNECTION_ID")) + .setRequireApproval(BinaryData.fromString("\"never\"")) + .setDeferLoading(true); + ToolSearchToolboxTool search = new ToolSearchToolboxTool(); + ToolboxVersionDetails version = toolboxesClient.createToolboxVersion(toolboxName, + Arrays.asList(innerMcp, search), "Tool-search toolbox", null, null, null); + // END: com.azure.ai.agents.toolboxes.ToolboxSearchToolboxSample.createToolboxSearchToolbox - ToolSearchToolboxTool toolboxSearchTool = new ToolSearchToolboxTool() - .setName("search_tools") - .setDescription("Search over available toolbox tools at runtime."); - - ToolboxVersionDetails version = toolboxesClient.createToolboxVersion( - toolboxName, - Collections.singletonList(toolboxSearchTool), - "Toolbox version with a Toolbox Search tool.", - null, - null, - null); + String toolboxUrl = endpoint + "/toolboxes/" + toolboxName + "/versions/" + + version.getVersion() + "/mcp?api-version=v1"; + String token = credential.getToken(new TokenRequestContext() + .addScopes("https://ai.azure.com/.default")).block().getToken(); + McpTool toolboxMcp = new McpTool("search-tool") + .setServerUrl(toolboxUrl) + .setAuthorization(token) + .setRequireApproval("never"); + agent = agentsClient.createAgentVersion("toolbox-search-agent", + new CreateAgentVersionInput(new PromptAgentDefinition(configuration.get("FOUNDRY_MODEL_NAME")) + .setInstructions("Use tool_search to discover a tool, then call_tool to invoke it.") + .setTools(java.util.Collections.singletonList(toolboxMcp)))); - System.out.printf("Created toolbox: %s%n", version.getName()); - System.out.printf("Toolbox version: %s%n", version.getVersion()); - for (ToolboxTool tool : version.getTools()) { - System.out.printf("Tool type: %s%n", tool.getType()); - } - - // END: com.azure.ai.agents.toolboxes.ToolboxSearchToolboxSample.createToolboxSearchToolbox + Response response = responsesClient.createAzureResponse( + new AzureCreateResponseOptions().setAgentReference( + new com.azure.ai.agents.models.AgentReference(agent.getName()).setVersion(agent.getVersion())), + ResponseCreateParams.builder().input("What is my GitHub profile username?")); + System.out.println("Response: " + response.output()); } finally { - try { - toolboxesClient.deleteToolbox(toolboxName); - System.out.printf("Deleted toolbox: %s%n", toolboxName); - } catch (ResourceNotFoundException ignored) { - // The sample toolbox may not have been created. + if (agent != null) { + agentsClient.deleteAgentVersion(agent.getName(), agent.getVersion()); } + deleteToolboxIfPresent(toolboxesClient, toolboxName); + } + } + + private static void deleteToolboxIfPresent(ToolboxesClient client, String toolboxName) { + try { + client.deleteToolbox(toolboxName); + } catch (ResourceNotFoundException ignored) { + // The toolbox does not exist. } } } diff --git a/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/tools/CodeInterpreterStructuredInputsSync.java b/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/tools/CodeInterpreterStructuredInputsSync.java new file mode 100644 index 0000000000000..b08fa90dbe315 --- /dev/null +++ b/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/tools/CodeInterpreterStructuredInputsSync.java @@ -0,0 +1,92 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.ai.agents.tools; + +import com.azure.ai.agents.AgentsClient; +import com.azure.ai.agents.AgentsClientBuilder; +import com.azure.ai.agents.ResponsesClient; +import com.azure.ai.agents.SampleUtils; +import com.azure.ai.agents.models.AgentVersionDetails; +import com.azure.ai.agents.models.AutoCodeInterpreterToolParameter; +import com.azure.ai.agents.models.AzureCreateResponseOptions; +import com.azure.ai.agents.models.CodeInterpreterTool; +import com.azure.ai.agents.models.CreateAgentVersionInput; +import com.azure.ai.agents.models.PromptAgentDefinition; +import com.azure.core.util.Configuration; +import com.azure.identity.DefaultAzureCredentialBuilder; +import com.azure.core.util.BinaryData; +import com.openai.client.OpenAIClient; +import com.openai.models.files.FileCreateParams; +import com.openai.models.files.FileObject; +import com.openai.models.files.FilePurpose; +import com.openai.models.responses.Response; +import com.openai.models.responses.ResponseCreateParams; +import com.openai.models.responses.ToolChoiceOptions; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * Demonstrates binding an uploaded file to a Code Interpreter tool through structured inputs. + * + *

Before running the sample, set these environment variables:

+ *
    + *
  • {@code FOUNDRY_PROJECT_ENDPOINT} - The Azure AI Project endpoint.
  • + *
  • {@code FOUNDRY_MODEL_NAME} - The model deployment name.
  • + *
+ */ +public class CodeInterpreterStructuredInputsSync { + public static void main(String[] args) throws Exception { + Configuration configuration = Configuration.getGlobalConfiguration(); + String endpoint = configuration.get("FOUNDRY_PROJECT_ENDPOINT"); + String model = configuration.get("FOUNDRY_MODEL_NAME"); + + AgentsClientBuilder builder = new AgentsClientBuilder() + .credential(new DefaultAzureCredentialBuilder().build()) + .endpoint(endpoint); + AgentsClient agentsClient = builder.buildAgentsClient(); + ResponsesClient responsesClient = builder.buildResponsesClient(); + OpenAIClient openAIClient = builder.buildOpenAIClient(); + + Path csv = SampleUtils.createTempFile("numbers", ".csv", "x\n1\n2\n3\n"); + FileObject uploaded = null; + AgentVersionDetails agent = null; + try { + uploaded = openAIClient.files().create(FileCreateParams.builder() + .file(csv).purpose(FilePurpose.ASSISTANTS).build()); + CodeInterpreterTool tool = new CodeInterpreterTool().setContainer( + new AutoCodeInterpreterToolParameter() + .setFileIds(Collections.singletonList("{{analysis_file_id}}"))); + PromptAgentDefinition definition = new PromptAgentDefinition(model) + .setInstructions("Read the bound CSV file and calculate the sum of x.") + .setTools(Collections.singletonList(tool)) + .setStructuredInputs(ToolSampleUtils.structuredInput("analysis_file_id", + "File ID available to Code Interpreter")); + agent = agentsClient.createAgentVersion("code-interpreter-structured-input", + new CreateAgentVersionInput(definition)); + + Map values = new LinkedHashMap<>(); + values.put("analysis_file_id", BinaryData.fromObject(uploaded.id())); + Response response = responsesClient.createAzureResponse( + new AzureCreateResponseOptions() + .setAgentReference(SampleUtils.toAgentReference(agent)) + .setStructuredInputs(values), + ResponseCreateParams.builder() + .input("Return the sum of x in numbers.csv.") + .toolChoice(ToolChoiceOptions.REQUIRED)); + SampleUtils.printResponseText(response); + } finally { + if (agent != null) { + agentsClient.deleteAgentVersion(agent.getName(), agent.getVersion()); + } + if (uploaded != null) { + openAIClient.files().delete(uploaded.id()); + } + Files.deleteIfExists(csv); + } + } +} diff --git a/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/tools/CodeInterpreterWithFilesAsync.java b/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/tools/CodeInterpreterWithFilesAsync.java new file mode 100644 index 0000000000000..c21b1ff6cc26f --- /dev/null +++ b/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/tools/CodeInterpreterWithFilesAsync.java @@ -0,0 +1,93 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.ai.agents.tools; + +import com.azure.ai.agents.AgentsAsyncClient; +import com.azure.ai.agents.AgentsClientBuilder; +import com.azure.ai.agents.ResponsesAsyncClient; +import com.azure.ai.agents.SampleUtils; +import com.azure.ai.agents.models.AgentVersionDetails; +import com.azure.ai.agents.models.AutoCodeInterpreterToolParameter; +import com.azure.ai.agents.models.AzureCreateResponseOptions; +import com.azure.ai.agents.models.CodeInterpreterTool; +import com.azure.ai.agents.models.CreateAgentVersionInput; +import com.azure.ai.agents.models.PromptAgentDefinition; +import com.azure.core.util.Configuration; +import com.azure.identity.DefaultAzureCredentialBuilder; +import com.openai.client.OpenAIClient; +import com.openai.models.files.FileCreateParams; +import com.openai.models.files.FileObject; +import com.openai.models.files.FilePurpose; +import com.openai.models.responses.ResponseCreateParams; +import reactor.core.publisher.Mono; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Collections; +import java.util.concurrent.atomic.AtomicReference; + +/** + * Demonstrates asynchronously using an uploaded file with Code Interpreter and downloading generated output. + * + *

Before running the sample, set these environment variables:

+ *
    + *
  • {@code FOUNDRY_PROJECT_ENDPOINT} - The Azure AI Project endpoint.
  • + *
  • {@code FOUNDRY_MODEL_NAME} - The model deployment name.
  • + *
+ */ +public class CodeInterpreterWithFilesAsync { + public static void main(String[] args) { + Configuration configuration = Configuration.getGlobalConfiguration(); + String endpoint = configuration.get("FOUNDRY_PROJECT_ENDPOINT"); + String model = configuration.get("FOUNDRY_MODEL_NAME"); + + AgentsClientBuilder builder = new AgentsClientBuilder() + .credential(new DefaultAzureCredentialBuilder().build()) + .endpoint(endpoint); + AgentsAsyncClient agentsClient = builder.buildAgentsAsyncClient(); + ResponsesAsyncClient responsesClient = builder.buildResponsesAsyncClient(); + OpenAIClient openAIClient = builder.buildOpenAIClient(); + Path csv = SampleUtils.createTempFile("quarterly-results", ".csv", + "quarter,revenue\nQ1,120\nQ2,150\nQ3,180\nQ4,210\n"); + FileObject uploaded = openAIClient.files().create(FileCreateParams.builder() + .file(csv).purpose(FilePurpose.ASSISTANTS).build()); + AtomicReference agentRef = new AtomicReference<>(); + + CodeInterpreterTool tool = new CodeInterpreterTool().setContainer( + new AutoCodeInterpreterToolParameter().setFileIds(Collections.singletonList(uploaded.id()))); + agentsClient.createAgentVersion("code-interpreter-files-async", + new CreateAgentVersionInput(new PromptAgentDefinition(model) + .setInstructions("Analyze uploaded data and create downloadable files when requested.") + .setTools(Collections.singletonList(tool)))) + .doOnNext(agentRef::set) + .flatMap(agent -> responsesClient.createAzureResponse( + new AzureCreateResponseOptions().setAgentReference(SampleUtils.toAgentReference(agent)), + ResponseCreateParams.builder().input("Create a CSV summary of quarterly revenue."))) + .doOnNext(SampleUtils::printResponseText) + .flatMap(response -> Mono.fromCallable(() -> { + CodeInterpreterWithFilesSync.downloadGeneratedFile(openAIClient, + ToolSampleUtils.findContainerFile(response)); + return response; + })) + .then(cleanup(agentsClient, openAIClient, uploaded, csv, agentRef)) + .onErrorResume(error -> cleanup(agentsClient, openAIClient, uploaded, csv, agentRef) + .then(Mono.error(error))) + .block(); + } + + private static Mono cleanup(AgentsAsyncClient agentsClient, OpenAIClient openAIClient, + FileObject uploaded, Path csv, AtomicReference agentRef) { + AgentVersionDetails agent = agentRef.get(); + Mono deleteAgent = agent == null ? Mono.empty() + : agentsClient.deleteAgentVersion(agent.getName(), agent.getVersion()); + return deleteAgent.then(Mono.fromRunnable(() -> { + openAIClient.files().delete(uploaded.id()); + try { + Files.deleteIfExists(csv); + } catch (Exception error) { + throw new RuntimeException(error); + } + })); + } +} diff --git a/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/tools/CodeInterpreterWithFilesSync.java b/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/tools/CodeInterpreterWithFilesSync.java new file mode 100644 index 0000000000000..196d4033ce873 --- /dev/null +++ b/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/tools/CodeInterpreterWithFilesSync.java @@ -0,0 +1,100 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.ai.agents.tools; + +import com.azure.ai.agents.AgentsClient; +import com.azure.ai.agents.AgentsClientBuilder; +import com.azure.ai.agents.ResponsesClient; +import com.azure.ai.agents.SampleUtils; +import com.azure.ai.agents.models.AgentVersionDetails; +import com.azure.ai.agents.models.AutoCodeInterpreterToolParameter; +import com.azure.ai.agents.models.AzureCreateResponseOptions; +import com.azure.ai.agents.models.CodeInterpreterTool; +import com.azure.ai.agents.models.CreateAgentVersionInput; +import com.azure.ai.agents.models.PromptAgentDefinition; +import com.azure.core.util.Configuration; +import com.azure.identity.DefaultAzureCredentialBuilder; +import com.openai.client.OpenAIClient; +import com.openai.core.http.HttpResponse; +import com.openai.models.containers.files.content.ContentRetrieveParams; +import com.openai.models.files.FileCreateParams; +import com.openai.models.files.FileObject; +import com.openai.models.files.FilePurpose; +import com.openai.models.responses.Response; +import com.openai.models.responses.ResponseCreateParams; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.util.Collections; + +/** + * Demonstrates using an uploaded file with Code Interpreter and downloading a generated file. + * + *

Before running the sample, set these environment variables:

+ *
    + *
  • {@code FOUNDRY_PROJECT_ENDPOINT} - The Azure AI Project endpoint.
  • + *
  • {@code FOUNDRY_MODEL_NAME} - The model deployment name.
  • + *
+ */ +public class CodeInterpreterWithFilesSync { + public static void main(String[] args) throws Exception { + Configuration configuration = Configuration.getGlobalConfiguration(); + String endpoint = configuration.get("FOUNDRY_PROJECT_ENDPOINT"); + String model = configuration.get("FOUNDRY_MODEL_NAME"); + + AgentsClientBuilder builder = new AgentsClientBuilder() + .credential(new DefaultAzureCredentialBuilder().build()) + .endpoint(endpoint); + AgentsClient agentsClient = builder.buildAgentsClient(); + ResponsesClient responsesClient = builder.buildResponsesClient(); + OpenAIClient openAIClient = builder.buildOpenAIClient(); + + Path csv = SampleUtils.createTempFile("quarterly-results", ".csv", + "quarter,revenue\nQ1,120\nQ2,150\nQ3,180\nQ4,210\n"); + FileObject uploaded = null; + AgentVersionDetails agent = null; + try { + uploaded = openAIClient.files().create(FileCreateParams.builder() + .file(csv).purpose(FilePurpose.ASSISTANTS).build()); + CodeInterpreterTool tool = new CodeInterpreterTool().setContainer( + new AutoCodeInterpreterToolParameter().setFileIds(Collections.singletonList(uploaded.id()))); + agent = agentsClient.createAgentVersion("code-interpreter-files", + new CreateAgentVersionInput(new PromptAgentDefinition(model) + .setInstructions("Analyze uploaded data and create downloadable files when requested.") + .setTools(Collections.singletonList(tool)))); + + Response response = responsesClient.createAzureResponse( + new AzureCreateResponseOptions().setAgentReference(SampleUtils.toAgentReference(agent)), + ResponseCreateParams.builder().input("Create a CSV summary of quarterly revenue.")); + SampleUtils.printResponseText(response); + downloadGeneratedFile(openAIClient, ToolSampleUtils.findContainerFile(response)); + } finally { + if (agent != null) { + agentsClient.deleteAgentVersion(agent.getName(), agent.getVersion()); + } + if (uploaded != null) { + openAIClient.files().delete(uploaded.id()); + } + Files.deleteIfExists(csv); + } + } + + static void downloadGeneratedFile(OpenAIClient client, ToolSampleUtils.ContainerFile generatedFile) + throws Exception { + if (generatedFile == null) { + System.out.println("No generated file was returned."); + return; + } + Path output = Files.createTempFile("agent-output-", "-" + generatedFile.getFilename()); + ContentRetrieveParams params = ContentRetrieveParams.builder() + .containerId(generatedFile.getContainerId()) + .fileId(generatedFile.getFileId()) + .build(); + try (HttpResponse content = client.containers().files().content().retrieve(params)) { + Files.copy(content.body(), output, StandardCopyOption.REPLACE_EXISTING); + } + System.out.println("Generated file downloaded to: " + output); + } +} diff --git a/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/tools/FileSearchStreamingAsync.java b/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/tools/FileSearchStreamingAsync.java new file mode 100644 index 0000000000000..e95cdf4d71e99 --- /dev/null +++ b/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/tools/FileSearchStreamingAsync.java @@ -0,0 +1,105 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.ai.agents.tools; + +import com.azure.ai.agents.AgentsAsyncClient; +import com.azure.ai.agents.AgentsClientBuilder; +import com.azure.ai.agents.ResponsesAsyncClient; +import com.azure.ai.agents.SampleUtils; +import com.azure.ai.agents.models.AgentVersionDetails; +import com.azure.ai.agents.models.AzureCreateResponseOptions; +import com.azure.ai.agents.models.CreateAgentVersionInput; +import com.azure.ai.agents.models.FileSearchTool; +import com.azure.ai.agents.models.PromptAgentDefinition; +import com.azure.core.util.Configuration; +import com.azure.identity.DefaultAzureCredentialBuilder; +import com.openai.client.OpenAIClient; +import com.openai.helpers.ResponseAccumulator; +import com.openai.models.files.FileCreateParams; +import com.openai.models.files.FileObject; +import com.openai.models.files.FilePurpose; +import com.openai.models.responses.ResponseCreateParams; +import com.openai.models.vectorstores.VectorStore; +import com.openai.models.vectorstores.VectorStoreCreateParams; +import reactor.core.publisher.Mono; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Collections; +import java.util.concurrent.atomic.AtomicReference; + +/** + * Demonstrates asynchronously streaming an agent response while File Search queries an uploaded document. + * + *

Before running the sample, set these environment variables:

+ *
    + *
  • {@code FOUNDRY_PROJECT_ENDPOINT} - The Azure AI Project endpoint.
  • + *
  • {@code FOUNDRY_MODEL_NAME} - The model deployment name.
  • + *
+ */ +public class FileSearchStreamingAsync { + public static void main(String[] args) { + Configuration configuration = Configuration.getGlobalConfiguration(); + String endpoint = configuration.get("FOUNDRY_PROJECT_ENDPOINT"); + String model = configuration.get("FOUNDRY_MODEL_NAME"); + + AgentsClientBuilder builder = new AgentsClientBuilder() + .credential(new DefaultAzureCredentialBuilder().build()) + .endpoint(endpoint); + AgentsAsyncClient agentsClient = builder.buildAgentsAsyncClient(); + ResponsesAsyncClient responsesClient = builder.buildResponsesAsyncClient(); + OpenAIClient openAIClient = builder.buildOpenAIClient(); + + Path document = SampleUtils.createTempFile("product-info", ".txt", + "Contoso Smart Eyewear provides navigation, translation, and hands-free notifications."); + FileObject uploaded = openAIClient.files().create(FileCreateParams.builder() + .file(document).purpose(FilePurpose.ASSISTANTS).build()); + VectorStore vectorStore = openAIClient.vectorStores().create(VectorStoreCreateParams.builder() + .name("ProductInfoStreamingStoreAsync") + .fileIds(Collections.singletonList(uploaded.id())) + .build()); + AtomicReference agentRef = new AtomicReference<>(); + ResponseAccumulator accumulator = ResponseAccumulator.create(); + + FileSearchTool tool = new FileSearchTool(Collections.singletonList(vectorStore.id())); + agentsClient.createAgentVersion("file-search-streaming-async", + new CreateAgentVersionInput(new PromptAgentDefinition(model) + .setInstructions("Search the product document before answering.") + .setTools(Collections.singletonList(tool)))) + .doOnNext(agentRef::set) + .flatMapMany(agent -> responsesClient.createStreamingAzureResponse( + new AzureCreateResponseOptions().setAgentReference(SampleUtils.toAgentReference(agent)), + ResponseCreateParams.builder().input("What features does Contoso Smart Eyewear provide?"))) + .doOnNext(event -> { + accumulator.accumulate(event); + event.fileSearchCallSearching().ifPresent(ignored -> + System.out.println("[Searching uploaded files]")); + event.outputTextDelta().ifPresent(delta -> System.out.print(delta.delta())); + }) + .then(Mono.fromRunnable(() -> { + System.out.println(); + SampleUtils.printResponseText(accumulator.response()); + })) + .then(cleanup(agentsClient, openAIClient, agentRef, uploaded, vectorStore, document)) + .onErrorResume(error -> cleanup(agentsClient, openAIClient, agentRef, uploaded, vectorStore, document) + .then(Mono.error(error))) + .block(); + } + + private static Mono cleanup(AgentsAsyncClient agentsClient, OpenAIClient openAIClient, + AtomicReference agentRef, FileObject uploaded, VectorStore vectorStore, Path document) { + AgentVersionDetails agent = agentRef.get(); + Mono deleteAgent = agent == null ? Mono.empty() + : agentsClient.deleteAgentVersion(agent.getName(), agent.getVersion()); + return deleteAgent.then(Mono.fromRunnable(() -> { + openAIClient.vectorStores().delete(vectorStore.id()); + openAIClient.files().delete(uploaded.id()); + try { + Files.deleteIfExists(document); + } catch (Exception error) { + throw new RuntimeException(error); + } + })); + } +} diff --git a/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/tools/FileSearchStreamingSync.java b/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/tools/FileSearchStreamingSync.java new file mode 100644 index 0000000000000..210c53467e6e3 --- /dev/null +++ b/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/tools/FileSearchStreamingSync.java @@ -0,0 +1,97 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.ai.agents.tools; + +import com.azure.ai.agents.AgentsClient; +import com.azure.ai.agents.AgentsClientBuilder; +import com.azure.ai.agents.ResponsesClient; +import com.azure.ai.agents.SampleUtils; +import com.azure.ai.agents.models.AgentVersionDetails; +import com.azure.ai.agents.models.AzureCreateResponseOptions; +import com.azure.ai.agents.models.CreateAgentVersionInput; +import com.azure.ai.agents.models.FileSearchTool; +import com.azure.ai.agents.models.PromptAgentDefinition; +import com.azure.core.util.Configuration; +import com.azure.identity.DefaultAzureCredentialBuilder; +import com.azure.core.util.IterableStream; +import com.openai.client.OpenAIClient; +import com.openai.helpers.ResponseAccumulator; +import com.openai.models.files.FileCreateParams; +import com.openai.models.files.FileObject; +import com.openai.models.files.FilePurpose; +import com.openai.models.responses.ResponseCreateParams; +import com.openai.models.responses.ResponseStreamEvent; +import com.openai.models.vectorstores.VectorStore; +import com.openai.models.vectorstores.VectorStoreCreateParams; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Collections; + +/** + * Demonstrates streaming an agent response while File Search queries an uploaded document. + * + *

Before running the sample, set these environment variables:

+ *
    + *
  • {@code FOUNDRY_PROJECT_ENDPOINT} - The Azure AI Project endpoint.
  • + *
  • {@code FOUNDRY_MODEL_NAME} - The model deployment name.
  • + *
+ */ +public class FileSearchStreamingSync { + public static void main(String[] args) throws Exception { + Configuration configuration = Configuration.getGlobalConfiguration(); + String endpoint = configuration.get("FOUNDRY_PROJECT_ENDPOINT"); + String model = configuration.get("FOUNDRY_MODEL_NAME"); + + AgentsClientBuilder builder = new AgentsClientBuilder() + .credential(new DefaultAzureCredentialBuilder().build()) + .endpoint(endpoint); + AgentsClient agentsClient = builder.buildAgentsClient(); + ResponsesClient responsesClient = builder.buildResponsesClient(); + OpenAIClient openAIClient = builder.buildOpenAIClient(); + + Path document = SampleUtils.createTempFile("product-info", ".txt", + "Contoso Smart Eyewear provides navigation, translation, and hands-free notifications."); + FileObject uploaded = null; + VectorStore vectorStore = null; + AgentVersionDetails agent = null; + try { + uploaded = openAIClient.files().create(FileCreateParams.builder() + .file(document).purpose(FilePurpose.ASSISTANTS).build()); + vectorStore = openAIClient.vectorStores().create(VectorStoreCreateParams.builder() + .name("ProductInfoStreamingStore") + .fileIds(Collections.singletonList(uploaded.id())) + .build()); + FileSearchTool tool = new FileSearchTool(Collections.singletonList(vectorStore.id())); + agent = agentsClient.createAgentVersion("file-search-streaming-agent", + new CreateAgentVersionInput(new PromptAgentDefinition(model) + .setInstructions("Search the product document before answering.") + .setTools(Collections.singletonList(tool)))); + + ResponseAccumulator accumulator = ResponseAccumulator.create(); + IterableStream events = responsesClient.createStreamingAzureResponse( + new AzureCreateResponseOptions().setAgentReference(SampleUtils.toAgentReference(agent)), + ResponseCreateParams.builder().input("What features does Contoso Smart Eyewear provide?")); + for (ResponseStreamEvent event : events) { + accumulator.accumulate(event); + event.fileSearchCallSearching().ifPresent(ignored -> + System.out.println("[Searching uploaded files]")); + event.outputTextDelta().ifPresent(delta -> System.out.print(delta.delta())); + } + System.out.println(); + SampleUtils.printResponseText(accumulator.response()); + } finally { + if (agent != null) { + agentsClient.deleteAgentVersion(agent.getName(), agent.getVersion()); + } + if (vectorStore != null) { + openAIClient.vectorStores().delete(vectorStore.id()); + } + if (uploaded != null) { + openAIClient.files().delete(uploaded.id()); + } + Files.deleteIfExists(document); + } + } +} diff --git a/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/tools/FileSearchStructuredInputsSync.java b/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/tools/FileSearchStructuredInputsSync.java new file mode 100644 index 0000000000000..fc254e7799ba5 --- /dev/null +++ b/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/tools/FileSearchStructuredInputsSync.java @@ -0,0 +1,104 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.ai.agents.tools; + +import com.azure.ai.agents.AgentsClient; +import com.azure.ai.agents.AgentsClientBuilder; +import com.azure.ai.agents.ResponsesClient; +import com.azure.ai.agents.SampleUtils; +import com.azure.ai.agents.models.AgentVersionDetails; +import com.azure.ai.agents.models.AzureCreateResponseOptions; +import com.azure.ai.agents.models.CreateAgentVersionInput; +import com.azure.ai.agents.models.FileSearchTool; +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.azure.core.util.BinaryData; +import com.openai.client.OpenAIClient; +import com.openai.models.files.FileCreateParams; +import com.openai.models.files.FileObject; +import com.openai.models.files.FilePurpose; +import com.openai.models.responses.Response; +import com.openai.models.responses.ResponseCreateParams; +import com.openai.models.vectorstores.VectorStore; +import com.openai.models.vectorstores.VectorStoreCreateParams; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * Demonstrates binding a vector store to File Search through structured inputs. + * + *

Before running the sample, set these environment variables:

+ *
    + *
  • {@code FOUNDRY_PROJECT_ENDPOINT} - The Azure AI Project endpoint.
  • + *
  • {@code FOUNDRY_MODEL_NAME} - The model deployment name.
  • + *
+ */ +public class FileSearchStructuredInputsSync { + public static void main(String[] args) throws Exception { + Configuration configuration = Configuration.getGlobalConfiguration(); + String endpoint = configuration.get("FOUNDRY_PROJECT_ENDPOINT"); + String model = configuration.get("FOUNDRY_MODEL_NAME"); + + AgentsClientBuilder builder = new AgentsClientBuilder() + .credential(new DefaultAzureCredentialBuilder().build()) + .endpoint(endpoint); + AgentsClient agentsClient = builder.buildAgentsClient(); + ResponsesClient responsesClient = builder.buildResponsesClient(); + OpenAIClient openAIClient = builder.buildOpenAIClient(); + + Path document = SampleUtils.createTempFile("product-info", ".txt", + "Contoso Smart Eyewear provides navigation, translation, and hands-free notifications."); + FileObject uploaded = null; + VectorStore vectorStore = null; + AgentVersionDetails agent = null; + try { + uploaded = openAIClient.files().create(FileCreateParams.builder() + .file(document).purpose(FilePurpose.ASSISTANTS).build()); + vectorStore = openAIClient.vectorStores().create(VectorStoreCreateParams.builder() + .name("ProductInfoStructuredStore") + .fileIds(Collections.singletonList(uploaded.id())) + .build()); + + Map definitions = new LinkedHashMap<>(); + definitions.putAll(ToolSampleUtils.structuredInput("vector_store_id", + "Vector store ID used by File Search")); + definitions.putAll(ToolSampleUtils.structuredInput("source_file_id", + "Source file ID for the prompt context")); + FileSearchTool tool = new FileSearchTool(Collections.singletonList("{{vector_store_id}}")); + PromptAgentDefinition definition = new PromptAgentDefinition(model) + .setInstructions("Search the bound vector store. The source file is {{source_file_id}}.") + .setTools(Collections.singletonList(tool)) + .setStructuredInputs(definitions); + agent = agentsClient.createAgentVersion("file-search-structured-input", + new CreateAgentVersionInput(definition)); + + Map values = new LinkedHashMap<>(); + values.put("vector_store_id", BinaryData.fromObject(vectorStore.id())); + values.put("source_file_id", BinaryData.fromObject(uploaded.id())); + Response response = responsesClient.createAzureResponse( + new AzureCreateResponseOptions() + .setAgentReference(SampleUtils.toAgentReference(agent)) + .setStructuredInputs(values), + ResponseCreateParams.builder().input("What features does Contoso Smart Eyewear provide?")); + SampleUtils.printResponseText(response); + } finally { + if (agent != null) { + agentsClient.deleteAgentVersion(agent.getName(), agent.getVersion()); + } + if (vectorStore != null) { + openAIClient.vectorStores().delete(vectorStore.id()); + } + if (uploaded != null) { + openAIClient.files().delete(uploaded.id()); + } + Files.deleteIfExists(document); + } + } +} diff --git a/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/tools/ToolSampleUtils.java b/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/tools/ToolSampleUtils.java new file mode 100644 index 0000000000000..3cd3b444afffd --- /dev/null +++ b/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/tools/ToolSampleUtils.java @@ -0,0 +1,90 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.ai.agents.tools; + +import com.azure.ai.agents.models.StructuredInputDefinition; +import com.openai.models.responses.Response; +import com.openai.models.responses.ResponseOutputItem; +import com.openai.models.responses.ResponseOutputMessage; +import com.openai.models.responses.ResponseOutputText; + +import java.util.LinkedHashMap; +import java.util.Map; + +final class ToolSampleUtils { + private ToolSampleUtils() { + } + + static Map structuredInput(String name, String description) { + Map definitions = new LinkedHashMap<>(); + definitions.put(name, new StructuredInputDefinition() + .setDescription(description) + .setRequired(true)); + return definitions; + } + + static void printUrlCitations(Response response) { + for (ResponseOutputItem item : response.output()) { + if (!item.message().isPresent()) { + continue; + } + for (ResponseOutputMessage.Content content : item.asMessage().content()) { + if (!content.outputText().isPresent()) { + continue; + } + for (ResponseOutputText.Annotation annotation : content.asOutputText().annotations()) { + if (annotation.isUrlCitation()) { + ResponseOutputText.Annotation.UrlCitation citation = annotation.asUrlCitation(); + System.out.printf("Citation: %s (%s)%n", citation.title(), citation.url()); + } + } + } + } + } + + static ContainerFile findContainerFile(Response response) { + for (ResponseOutputItem item : response.output()) { + if (!item.message().isPresent()) { + continue; + } + for (ResponseOutputMessage.Content content : item.asMessage().content()) { + if (!content.outputText().isPresent()) { + continue; + } + for (ResponseOutputText.Annotation annotation : content.asOutputText().annotations()) { + if (annotation.isContainerFileCitation()) { + ResponseOutputText.Annotation.ContainerFileCitation citation + = annotation.asContainerFileCitation(); + return new ContainerFile(citation.containerId(), citation.fileId(), citation.filename()); + } + } + } + } + return null; + } + + static final class ContainerFile { + private final String containerId; + private final String fileId; + private final String filename; + + ContainerFile(String containerId, String fileId, String filename) { + this.containerId = containerId; + this.fileId = fileId; + this.filename = filename; + } + + String getContainerId() { + return containerId; + } + + String getFileId() { + return fileId; + } + + String getFilename() { + return filename; + } + } +} diff --git a/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/tools/WebSearchCustomSearchSync.java b/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/tools/WebSearchCustomSearchSync.java new file mode 100644 index 0000000000000..994c53b454da5 --- /dev/null +++ b/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/tools/WebSearchCustomSearchSync.java @@ -0,0 +1,71 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.ai.agents.tools; + +import com.azure.ai.agents.AgentsClient; +import com.azure.ai.agents.AgentsClientBuilder; +import com.azure.ai.agents.ResponsesClient; +import com.azure.ai.agents.SampleUtils; +import com.azure.ai.agents.models.AgentVersionDetails; +import com.azure.ai.agents.models.AzureCreateResponseOptions; +import com.azure.ai.agents.models.CreateAgentVersionInput; +import com.azure.ai.agents.models.PromptAgentDefinition; +import com.azure.ai.agents.models.WebSearchConfiguration; +import com.azure.ai.agents.models.WebSearchTool; +import com.azure.core.util.Configuration; +import com.azure.identity.DefaultAzureCredentialBuilder; +import com.openai.models.responses.Response; +import com.openai.models.responses.ResponseCreateParams; +import com.openai.models.responses.ToolChoiceOptions; + +import java.util.Collections; + +/** + * Demonstrates configuring Web Search with a Bing Custom Search connection. + * + *

Before running the sample, set these environment variables:

+ *
    + *
  • {@code FOUNDRY_PROJECT_ENDPOINT} - The Azure AI Project endpoint.
  • + *
  • {@code FOUNDRY_MODEL_NAME} - The model deployment name.
  • + *
  • {@code BING_CUSTOM_SEARCH_PROJECT_CONNECTION_ID} - The Bing Custom Search project connection ID.
  • + *
  • {@code BING_CUSTOM_SEARCH_INSTANCE_NAME} - The Bing Custom Search instance name.
  • + *
  • {@code BING_CUSTOM_USER_INPUT} - Optional. The question submitted to Bing Custom Search. Defaults to {@code What are the latest product updates?}.
  • + *
+ */ +public class WebSearchCustomSearchSync { + public static void main(String[] args) { + Configuration configuration = Configuration.getGlobalConfiguration(); + String endpoint = configuration.get("FOUNDRY_PROJECT_ENDPOINT"); + String model = configuration.get("FOUNDRY_MODEL_NAME"); + + AgentsClientBuilder builder = new AgentsClientBuilder() + .credential(new DefaultAzureCredentialBuilder().build()) + .endpoint(endpoint); + AgentsClient agentsClient = builder.buildAgentsClient(); + ResponsesClient responsesClient = builder.buildResponsesClient(); + AgentVersionDetails agent = null; + + try { + WebSearchTool tool = new WebSearchTool().setCustomSearchConfiguration( + new WebSearchConfiguration( + configuration.get("BING_CUSTOM_SEARCH_PROJECT_CONNECTION_ID"), + configuration.get("BING_CUSTOM_SEARCH_INSTANCE_NAME"))); + agent = agentsClient.createAgentVersion("web-search-custom-agent", + new CreateAgentVersionInput(new PromptAgentDefinition(model) + .setInstructions("Use the configured custom search source and cite results.") + .setTools(Collections.singletonList(tool)))); + Response response = responsesClient.createAzureResponse( + new AzureCreateResponseOptions().setAgentReference(SampleUtils.toAgentReference(agent)), + ResponseCreateParams.builder() + .input(configuration.get("BING_CUSTOM_USER_INPUT", "What are the latest product updates?")) + .toolChoice(ToolChoiceOptions.REQUIRED)); + SampleUtils.printResponseText(response); + ToolSampleUtils.printUrlCitations(response); + } finally { + if (agent != null) { + agentsClient.deleteAgentVersion(agent.getName(), agent.getVersion()); + } + } + } +} diff --git a/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/tools/WebSearchSync.java b/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/tools/WebSearchSync.java index 7d7c4a6fbb8e1..3fba6128271f2 100644 --- a/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/tools/WebSearchSync.java +++ b/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/tools/WebSearchSync.java @@ -6,83 +6,75 @@ import com.azure.ai.agents.AgentsClient; import com.azure.ai.agents.AgentsClientBuilder; import com.azure.ai.agents.ResponsesClient; -import com.azure.ai.agents.models.AgentReference; -import com.azure.ai.agents.models.AzureCreateResponseOptions; +import com.azure.ai.agents.SampleUtils; import com.azure.ai.agents.models.AgentVersionDetails; +import com.azure.ai.agents.models.ApproximateLocation; +import com.azure.ai.agents.models.AzureCreateResponseOptions; import com.azure.ai.agents.models.CreateAgentVersionInput; import com.azure.ai.agents.models.PromptAgentDefinition; import com.azure.ai.agents.models.WebSearchPreviewTool; import com.azure.core.util.Configuration; import com.azure.identity.DefaultAzureCredentialBuilder; +import com.azure.core.util.IterableStream; +import com.openai.helpers.ResponseAccumulator; import com.openai.models.responses.Response; import com.openai.models.responses.ResponseCreateParams; -import com.openai.models.responses.ResponseOutputItem; -import com.openai.models.responses.ResponseOutputMessage; +import com.openai.models.responses.ResponseStreamEvent; +import com.openai.models.responses.ToolChoiceOptions; import java.util.Collections; /** - * This sample demonstrates how to create an agent with the Web Search tool - * to search the web for current information. + * Demonstrates streaming a response from the preview Web Search tool with approximate location and citations. * *

Before running the sample, set these environment variables:

*
    - *
  • FOUNDRY_PROJECT_ENDPOINT - The Azure AI Project endpoint.
  • - *
  • FOUNDRY_MODEL_NAME - The model deployment name.
  • + *
  • {@code FOUNDRY_PROJECT_ENDPOINT} - The Azure AI Project endpoint.
  • + *
  • {@code FOUNDRY_MODEL_NAME} - The model deployment name.
  • *
*/ public class WebSearchSync { public static void main(String[] args) { - String endpoint = Configuration.getGlobalConfiguration().get("FOUNDRY_PROJECT_ENDPOINT"); - String model = Configuration.getGlobalConfiguration().get("FOUNDRY_MODEL_NAME"); + Configuration configuration = Configuration.getGlobalConfiguration(); + String endpoint = configuration.get("FOUNDRY_PROJECT_ENDPOINT"); + String model = configuration.get("FOUNDRY_MODEL_NAME"); AgentsClientBuilder builder = new AgentsClientBuilder() .credential(new DefaultAzureCredentialBuilder().build()) .endpoint(endpoint); - AgentsClient agentsClient = builder.buildAgentsClient(); ResponsesClient responsesClient = builder.buildResponsesClient(); - AgentVersionDetails agent = null; try { // BEGIN: com.azure.ai.agents.define_web_search - // Create a WebSearchPreviewTool - WebSearchPreviewTool tool = new WebSearchPreviewTool(); + WebSearchPreviewTool tool = new WebSearchPreviewTool() + .setUserLocation(new ApproximateLocation() + .setCountry("GB") + .setRegion("London") + .setCity("London")); // END: com.azure.ai.agents.define_web_search + agent = agentsClient.createAgentVersion("web-search-preview-agent", + new CreateAgentVersionInput(new PromptAgentDefinition(model) + .setInstructions("Search the web for current information and cite sources.") + .setTools(Collections.singletonList(tool)))); - // Create the agent definition with Web Search tool enabled - PromptAgentDefinition agentDefinition = new PromptAgentDefinition(model) - .setInstructions("You are a helpful assistant that can perform web searches to find information. " - + "When asked to find information, use the web search tool to gather relevant data.") - .setTools(Collections.singletonList(tool)); - - agent = agentsClient.createAgentVersion("web-search-agent", new CreateAgentVersionInput(agentDefinition)); - System.out.printf("Agent created: %s (version %s)%n", agent.getName(), agent.getVersion()); - - AgentReference agentReference = new AgentReference(agent.getName()) - .setVersion(agent.getVersion()); - - Response response = responsesClient.createAzureResponse( - new AzureCreateResponseOptions().setAgentReference(agentReference), + ResponseAccumulator accumulator = ResponseAccumulator.create(); + IterableStream events = responsesClient.createStreamingAzureResponse( + new AzureCreateResponseOptions().setAgentReference(SampleUtils.toAgentReference(agent)), ResponseCreateParams.builder() - .input("What are the latest trends in renewable energy?")); - - // Process and display the response - for (ResponseOutputItem outputItem : response.output()) { - if (outputItem.message().isPresent()) { - ResponseOutputMessage message = outputItem.message().get(); - message.content().forEach(content -> { - content.outputText().ifPresent(text -> { - System.out.println("Assistant: " + text.text()); - }); - }); - } + .input("Show the latest London Underground service updates.") + .toolChoice(ToolChoiceOptions.REQUIRED)); + for (ResponseStreamEvent event : events) { + accumulator.accumulate(event); + event.outputTextDelta().ifPresent(delta -> System.out.print(delta.delta())); } + System.out.println(); + Response response = accumulator.response(); + ToolSampleUtils.printUrlCitations(response); } finally { if (agent != null) { agentsClient.deleteAgentVersion(agent.getName(), agent.getVersion()); - System.out.println("Agent deleted"); } } } diff --git a/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/tools/WebSearchToolSync.java b/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/tools/WebSearchToolSync.java new file mode 100644 index 0000000000000..68fc02531ef21 --- /dev/null +++ b/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/tools/WebSearchToolSync.java @@ -0,0 +1,69 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.ai.agents.tools; + +import com.azure.ai.agents.AgentsClient; +import com.azure.ai.agents.AgentsClientBuilder; +import com.azure.ai.agents.ResponsesClient; +import com.azure.ai.agents.SampleUtils; +import com.azure.ai.agents.models.AgentVersionDetails; +import com.azure.ai.agents.models.AzureCreateResponseOptions; +import com.azure.ai.agents.models.CreateAgentVersionInput; +import com.azure.ai.agents.models.PromptAgentDefinition; +import com.azure.ai.agents.models.WebSearchApproximateLocation; +import com.azure.ai.agents.models.WebSearchTool; +import com.azure.core.util.Configuration; +import com.azure.identity.DefaultAzureCredentialBuilder; +import com.openai.models.responses.Response; +import com.openai.models.responses.ResponseCreateParams; +import com.openai.models.responses.ToolChoiceOptions; + +import java.util.Collections; + +/** + * Demonstrates using the Web Search tool with an approximate user location. + * + *

Before running the sample, set these environment variables:

+ *
    + *
  • {@code FOUNDRY_PROJECT_ENDPOINT} - The Azure AI Project endpoint.
  • + *
  • {@code FOUNDRY_MODEL_NAME} - The model deployment name.
  • + *
+ */ +public class WebSearchToolSync { + public static void main(String[] args) { + Configuration configuration = Configuration.getGlobalConfiguration(); + String endpoint = configuration.get("FOUNDRY_PROJECT_ENDPOINT"); + String model = configuration.get("FOUNDRY_MODEL_NAME"); + + AgentsClientBuilder builder = new AgentsClientBuilder() + .credential(new DefaultAzureCredentialBuilder().build()) + .endpoint(endpoint); + AgentsClient agentsClient = builder.buildAgentsClient(); + ResponsesClient responsesClient = builder.buildResponsesClient(); + AgentVersionDetails agent = null; + + try { + WebSearchTool tool = new WebSearchTool() + .setUserLocation(new WebSearchApproximateLocation() + .setCountry("GB") + .setRegion("London") + .setCity("London")); + agent = agentsClient.createAgentVersion("web-search-agent", + new CreateAgentVersionInput(new PromptAgentDefinition(model) + .setInstructions("Search the web for current information and cite sources.") + .setTools(Collections.singletonList(tool)))); + Response response = responsesClient.createAzureResponse( + new AzureCreateResponseOptions().setAgentReference(SampleUtils.toAgentReference(agent)), + ResponseCreateParams.builder() + .input("Show the latest London Underground service updates.") + .toolChoice(ToolChoiceOptions.REQUIRED)); + SampleUtils.printResponseText(response); + ToolSampleUtils.printUrlCitations(response); + } finally { + if (agent != null) { + agentsClient.deleteAgentVersion(agent.getName(), agent.getVersion()); + } + } + } +} diff --git a/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/voice/VoiceAgentBasicAsyncSample.java b/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/voice/VoiceAgentBasicAsyncSample.java new file mode 100644 index 0000000000000..9e6c6beac7075 --- /dev/null +++ b/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/voice/VoiceAgentBasicAsyncSample.java @@ -0,0 +1,64 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.ai.agents.voice; + +import com.azure.ai.agents.AgentsAsyncClient; +import com.azure.ai.agents.AgentsClientBuilder; +import com.azure.core.util.Configuration; +import com.azure.identity.DefaultAzureCredentialBuilder; +import com.azure.ai.agents.models.AgentKind; +import com.azure.ai.agents.models.CreateAgentVersionInput; +import com.azure.ai.agents.models.VoiceModelType; +import reactor.core.publisher.Mono; + +/** + * Demonstrates the asynchronous voice-agent lifecycle. + * + *

Before running the sample, set these environment variables:

+ *
    + *
  • {@code FOUNDRY_PROJECT_ENDPOINT} - The Azure AI Project endpoint.
  • + *
  • {@code FOUNDRY_VOICE_MODEL} - Optional. The voice model or deployment name. Defaults to {@code gpt-realtime}.
  • + *
  • {@code FOUNDRY_VOICE_MODEL_TYPE} - Optional. The voice model type. Defaults to {@code managed}.
  • + *
  • {@code FOUNDRY_VOICE_AGENT_NAME} - The voice agent name. Defaults to {@code voice-agent-async-java}.
  • + *
+ */ +public class VoiceAgentBasicAsyncSample { + public static void main(String[] args) { + Configuration configuration = Configuration.getGlobalConfiguration(); + String endpoint = configuration.get("FOUNDRY_PROJECT_ENDPOINT"); + String model = configuration.get("FOUNDRY_VOICE_MODEL", "gpt-realtime"); + VoiceModelType modelType = VoiceModelType.fromString(configuration.get( + "FOUNDRY_VOICE_MODEL_TYPE", VoiceModelType.MANAGED.toString())); + String agentName = configuration.get("FOUNDRY_VOICE_AGENT_NAME", "voice-agent-async-java"); + + AgentsAsyncClient client = new AgentsClientBuilder() + .credential(new DefaultAzureCredentialBuilder().build()) + .endpoint(endpoint) + .allowPreview(true) + .buildAgentsAsyncClient(); + + client.createAgentVersion(agentName, + new CreateAgentVersionInput(VoiceAgentSampleUtils.createDefinition(modelType, model, + "You are a friendly voice assistant. Keep replies short and natural."))) + .doOnNext(created -> System.out.printf("Created voice agent %s, version %s%n", + created.getName(), created.getVersion())) + .then(client.getAgent(agentName)) + .doOnNext(agent -> System.out.printf("Retrieved voice agent %s, state %s%n", + agent.getName(), agent.getState())) + .thenMany(client.listAgents(AgentKind.VOICE, null, null, null, null)) + .doOnNext(agent -> System.out.println("Voice agent: " + agent.getName())) + .then(client.createAgentVersion(agentName, + new CreateAgentVersionInput(VoiceAgentSampleUtils.createDefinition(modelType, model, + "You are a friendly voice assistant. Always greet the caller warmly.")) + .setDescription("Updated voice-agent instructions."))) + .doOnNext(updated -> System.out.println("Created updated version: " + updated.getVersion())) + .then(client.disableAgent(agentName)) + .then(client.enableAgent(agentName)) + .then(client.deleteAgent(agentName)) + .onErrorResume(error -> client.deleteAgent(agentName) + .onErrorResume(cleanupError -> Mono.empty()) + .then(Mono.error(error))) + .block(); + } +} diff --git a/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/voice/VoiceAgentBasicSample.java b/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/voice/VoiceAgentBasicSample.java new file mode 100644 index 0000000000000..e03ab19962386 --- /dev/null +++ b/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/voice/VoiceAgentBasicSample.java @@ -0,0 +1,67 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.ai.agents.voice; + +import com.azure.ai.agents.AgentsClient; +import com.azure.ai.agents.AgentsClientBuilder; +import com.azure.core.util.Configuration; +import com.azure.identity.DefaultAzureCredentialBuilder; +import com.azure.ai.agents.models.AgentDetails; +import com.azure.ai.agents.models.AgentKind; +import com.azure.ai.agents.models.AgentVersionDetails; +import com.azure.ai.agents.models.CreateAgentVersionInput; +import com.azure.ai.agents.models.VoiceModelType; + +/** + * Demonstrates the synchronous voice-agent lifecycle. + * + *

Before running the sample, set these environment variables:

+ *
    + *
  • {@code FOUNDRY_PROJECT_ENDPOINT} - The Azure AI Project endpoint.
  • + *
  • {@code FOUNDRY_VOICE_MODEL} - Optional. The voice model or deployment name. Defaults to {@code gpt-realtime}.
  • + *
  • {@code FOUNDRY_VOICE_MODEL_TYPE} - Optional. The voice model type. Defaults to {@code managed}.
  • + *
  • {@code FOUNDRY_VOICE_AGENT_NAME} - The voice agent name. Defaults to {@code voice-agent-java}.
  • + *
+ */ +public class VoiceAgentBasicSample { + public static void main(String[] args) { + Configuration configuration = Configuration.getGlobalConfiguration(); + String endpoint = configuration.get("FOUNDRY_PROJECT_ENDPOINT"); + String model = configuration.get("FOUNDRY_VOICE_MODEL", "gpt-realtime"); + VoiceModelType modelType = VoiceModelType.fromString(configuration.get( + "FOUNDRY_VOICE_MODEL_TYPE", VoiceModelType.MANAGED.toString())); + String agentName = configuration.get("FOUNDRY_VOICE_AGENT_NAME", "voice-agent-java"); + + AgentsClient client = new AgentsClientBuilder() + .credential(new DefaultAzureCredentialBuilder().build()) + .endpoint(endpoint) + .allowPreview(true) + .buildAgentsClient(); + try { + AgentVersionDetails created = client.createAgentVersion(agentName, + new CreateAgentVersionInput(VoiceAgentSampleUtils.createDefinition(modelType, model, + "You are a friendly voice assistant. Keep replies short and natural."))); + System.out.printf("Created voice agent %s, version %s%n", created.getName(), created.getVersion()); + + AgentDetails agent = client.getAgent(agentName); + System.out.printf("Retrieved voice agent %s, state %s%n", agent.getName(), agent.getState()); + for (AgentDetails item : client.listAgents(AgentKind.VOICE, null, null, null, null)) { + System.out.println("Voice agent: " + item.getName()); + } + + AgentVersionDetails updated = client.createAgentVersion(agentName, + new CreateAgentVersionInput(VoiceAgentSampleUtils.createDefinition(modelType, model, + "You are a friendly voice assistant. Always greet the caller warmly.")) + .setDescription("Updated voice-agent instructions.")); + System.out.println("Created updated version: " + updated.getVersion()); + client.disableAgent(agentName); + System.out.println("Disabled voice agent"); + client.enableAgent(agentName); + System.out.println("Enabled voice agent"); + } finally { + client.deleteAgent(agentName); + System.out.println("Deleted voice agent: " + agentName); + } + } +} diff --git a/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/voice/VoiceAgentGenerateSample.java b/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/voice/VoiceAgentGenerateSample.java new file mode 100644 index 0000000000000..0d8d590e91452 --- /dev/null +++ b/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/voice/VoiceAgentGenerateSample.java @@ -0,0 +1,54 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.ai.agents.voice; + +import com.azure.ai.agents.AgentsClient; +import com.azure.ai.agents.AgentsClientBuilder; +import com.azure.core.util.Configuration; +import com.azure.identity.DefaultAzureCredentialBuilder; +import com.azure.ai.agents.models.AgentDetails; +import com.azure.ai.agents.models.AgentVersionDetails; +import com.azure.ai.agents.models.VoiceAgentDefinition; +import com.azure.core.util.BinaryData; + +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * Demonstrates guided authoring of a voice agent through the agent generation API. + * + *

Before running the sample, set these environment variables:

+ *
    + *
  • {@code FOUNDRY_PROJECT_ENDPOINT} - The Azure AI Project endpoint.
  • + *
  • {@code FOUNDRY_VOICE_AGENT_NAME} - The voice agent name. Defaults to {@code generated-voice-agent-java}.
  • + *
+ */ +public class VoiceAgentGenerateSample { + public static void main(String[] args) { + Configuration configuration = Configuration.getGlobalConfiguration(); + String endpoint = configuration.get("FOUNDRY_PROJECT_ENDPOINT"); + String agentName = configuration.get("FOUNDRY_VOICE_AGENT_NAME", "generated-voice-agent-java"); + + AgentsClient client = new AgentsClientBuilder() + .credential(new DefaultAzureCredentialBuilder().build()) + .endpoint(endpoint) + .allowPreview(true) + .buildAgentsClient(); + + Map request = new LinkedHashMap<>(); + request.put("kind", "voice"); + request.put("name", agentName); + AgentDetails generated = client.generateAgent(BinaryData.fromObject(request)); + try { + System.out.println("Generated voice agent: " + generated.getName()); + AgentVersionDetails latest = generated.getVersions().getLatest(); + if (latest != null && latest.getDefinition() instanceof VoiceAgentDefinition) { + VoiceAgentDefinition definition = (VoiceAgentDefinition) latest.getDefinition(); + System.out.println("Instructions: " + definition.getInstructions()); + } + } finally { + client.deleteAgent(generated.getName()); + } + } +} diff --git a/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/voice/VoiceAgentReadConversationAudioSample.java b/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/voice/VoiceAgentReadConversationAudioSample.java new file mode 100644 index 0000000000000..82572695b212f --- /dev/null +++ b/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/voice/VoiceAgentReadConversationAudioSample.java @@ -0,0 +1,78 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.ai.agents.voice; + +import com.azure.ai.agents.BetaAgentEndpointConversationsClient; +import com.azure.ai.agents.AgentsClientBuilder; +import com.azure.ai.agents.models.VoiceItemAudioResponse; +import com.azure.ai.agents.models.VoiceRecordingResponse; +import com.azure.core.exception.ResourceNotFoundException; +import com.azure.core.http.rest.RequestOptions; +import com.azure.core.util.BinaryData; +import com.azure.core.util.Configuration; +import com.azure.identity.DefaultAzureCredentialBuilder; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Map; + +/** + * Demonstrates downloading whole-call and item-level audio from a persisted voice conversation. + * + *

Before running the sample, set these environment variables:

+ *
    + *
  • {@code FOUNDRY_PROJECT_ENDPOINT} - The Azure AI Project endpoint.
  • + *
  • {@code FOUNDRY_VOICE_AGENT_NAME} - The voice agent name.
  • + *
  • {@code FOUNDRY_VOICE_CONVERSATION_ID} - The persisted voice conversation ID.
  • + *
+ */ +public class VoiceAgentReadConversationAudioSample { + public static void main(String[] args) throws IOException { + Configuration configuration = Configuration.getGlobalConfiguration(); + String endpoint = configuration.get("FOUNDRY_PROJECT_ENDPOINT"); + String agentName = configuration.get("FOUNDRY_VOICE_AGENT_NAME"); + String conversationId = configuration.get("FOUNDRY_VOICE_CONVERSATION_ID"); + BetaAgentEndpointConversationsClient conversations = new AgentsClientBuilder() + .credential(new DefaultAzureCredentialBuilder().build()) + .endpoint(endpoint) + .buildBetaAgentEndpointConversationsClient(); + + VoiceRecordingResponse recording = conversations.getAgentConversationAudio(agentName, conversationId); + System.out.printf("Recording: format=%s, rate=%d, channels=%d, duration=%s%n", + recording.getFormat(), recording.getSampleRate(), recording.getChannels(), recording.getDurationMs()); + if (recording.getBlobUri() != null) { + System.out.println("Recording is stored in customer storage: " + recording.getBlobUri()); + } else { + Path output = Files.createTempFile(conversationId + "-", ".wav"); + Files.write(output, conversations.getAgentConversationAudioContent(agentName, conversationId).toBytes()); + System.out.println("Wrote merged recording: " + output); + } + + for (BinaryData itemData : conversations.listAgentConversationItems(agentName, conversationId, + new RequestOptions())) { + @SuppressWarnings("unchecked") + Map item = itemData.toObject(Map.class); + String itemId = (String) item.get("id"); + if (itemId == null) { + continue; + } + try { + VoiceItemAudioResponse metadata = conversations.getAgentConversationItemAudio( + agentName, conversationId, itemId); + if (metadata.getBlobUri() != null) { + System.out.println("Item audio is stored in customer storage: " + metadata.getBlobUri()); + } else { + Path output = Files.createTempFile(conversationId + "-" + itemId + "-", ".wav"); + Files.write(output, conversations.getAgentConversationItemAudioContent( + agentName, conversationId, itemId).toBytes()); + System.out.println("Wrote item audio: " + output); + } + break; + } catch (ResourceNotFoundException ignored) { + // This transcript item has no persisted audio. + } + } + } +} diff --git a/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/voice/VoiceAgentReadConversationSample.java b/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/voice/VoiceAgentReadConversationSample.java new file mode 100644 index 0000000000000..d63e6f4438520 --- /dev/null +++ b/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/voice/VoiceAgentReadConversationSample.java @@ -0,0 +1,67 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.ai.agents.voice; + +import com.azure.ai.agents.BetaAgentEndpointConversationsClient; +import com.azure.ai.agents.AgentsClientBuilder; +import com.azure.ai.agents.models.RealtimeConversationItem; +import com.azure.ai.agents.models.VoiceConversation; +import com.azure.ai.agents.models.VoiceResponse; +import com.azure.core.http.rest.RequestOptions; +import com.azure.core.util.BinaryData; +import com.azure.core.util.Configuration; +import com.azure.identity.DefaultAzureCredentialBuilder; + +import java.util.Map; + +/** + * Demonstrates reading a persisted voice conversation, its responses, and transcript items. + * + *

Before running the sample, set these environment variables:

+ *
    + *
  • {@code FOUNDRY_PROJECT_ENDPOINT} - The Azure AI Project endpoint.
  • + *
  • {@code FOUNDRY_VOICE_AGENT_NAME} - The voice agent name.
  • + *
  • {@code FOUNDRY_VOICE_CONVERSATION_ID} - The persisted voice conversation ID.
  • + *
+ */ +public class VoiceAgentReadConversationSample { + public static void main(String[] args) { + Configuration configuration = Configuration.getGlobalConfiguration(); + String endpoint = configuration.get("FOUNDRY_PROJECT_ENDPOINT"); + String agentName = configuration.get("FOUNDRY_VOICE_AGENT_NAME"); + String conversationId = configuration.get("FOUNDRY_VOICE_CONVERSATION_ID"); + BetaAgentEndpointConversationsClient conversations = new AgentsClientBuilder() + .credential(new DefaultAzureCredentialBuilder().build()) + .endpoint(endpoint) + .buildBetaAgentEndpointConversationsClient(); + + VoiceConversation conversation = conversations.getAgentConversation(agentName, conversationId); + System.out.printf("Conversation %s: status=%s, created=%s, usage=%s%n", + conversation.getId(), conversation.getStatus(), conversation.getCreatedAt(), conversation.getUsage()); + + for (VoiceResponse response : conversations.listAgentConversationResponses(agentName, conversationId)) { + VoiceResponse detail = conversations.getAgentConversationResponse(agentName, conversationId, + response.getId()); + System.out.printf("Response %s: status=%s, usage=%s%n", + detail.getId(), detail.getStatus(), detail.getUsage()); + for (RealtimeConversationItem item : conversations.listAgentConversationResponseItems( + agentName, conversationId, response.getId())) { + System.out.println(" Response item type: " + item.getType()); + } + } + + for (BinaryData itemData : conversations.listAgentConversationItems(agentName, conversationId, + new RequestOptions())) { + @SuppressWarnings("unchecked") + Map item = itemData.toObject(Map.class); + String itemId = (String) item.get("id"); + System.out.printf("Transcript item: type=%s, id=%s%n", item.get("type"), itemId); + if (itemId != null) { + RealtimeConversationItem fetched = conversations.getAgentConversationItem(agentName, + conversationId, itemId); + System.out.println(" Fetched item type: " + fetched.getType()); + } + } + } +} diff --git a/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/voice/VoiceAgentSampleUtils.java b/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/voice/VoiceAgentSampleUtils.java new file mode 100644 index 0000000000000..2ddcf80d3bbb0 --- /dev/null +++ b/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/voice/VoiceAgentSampleUtils.java @@ -0,0 +1,29 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.ai.agents.voice; + +import com.azure.ai.agents.models.VoiceAgentAudioConfig; +import com.azure.ai.agents.models.VoiceAgentAudioOutputConfig; +import com.azure.ai.agents.models.VoiceAgentDefinition; +import com.azure.ai.agents.models.VoiceModelType; +import com.azure.ai.agents.models.VoiceOutputModality; +import com.azure.ai.agents.models.VoiceType; + +import java.util.Collections; + +final class VoiceAgentSampleUtils { + private VoiceAgentSampleUtils() { + } + + static VoiceAgentDefinition createDefinition(VoiceModelType modelType, String model, String instructions) { + VoiceAgentAudioOutputConfig output = new VoiceAgentAudioOutputConfig() + .setVoice("en-US-AvaNeural") + .setVoiceType(VoiceType.AZURE_STANDARD); + return new VoiceAgentDefinition(modelType, model) + .setInstructions(instructions) + .setAudio(new VoiceAgentAudioConfig().setOutput(output)) + .setOutputModalities(Collections.singletonList(VoiceOutputModality.AUDIO)) + .setStore(true); + } +} diff --git a/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/voice/VoiceAgentVersionsSample.java b/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/voice/VoiceAgentVersionsSample.java new file mode 100644 index 0000000000000..a7b7c2d381f4c --- /dev/null +++ b/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/voice/VoiceAgentVersionsSample.java @@ -0,0 +1,69 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.ai.agents.voice; + +import com.azure.ai.agents.AgentsClient; +import com.azure.ai.agents.AgentsClientBuilder; +import com.azure.core.util.Configuration; +import com.azure.identity.DefaultAzureCredentialBuilder; +import com.azure.ai.agents.models.AgentVersionDetails; +import com.azure.ai.agents.models.CreateAgentVersionInput; +import com.azure.ai.agents.models.VoiceModelType; + +/** + * Demonstrates released and draft voice-agent versions. + * + *

Before running the sample, set these environment variables:

+ *
    + *
  • {@code FOUNDRY_PROJECT_ENDPOINT} - The Azure AI Project endpoint.
  • + *
  • {@code FOUNDRY_VOICE_MODEL} - Optional. The voice model or deployment name. Defaults to {@code gpt-realtime}.
  • + *
  • {@code FOUNDRY_VOICE_MODEL_TYPE} - Optional. The voice model type. Defaults to {@code managed}.
  • + *
  • {@code FOUNDRY_VOICE_AGENT_NAME} - The voice agent name. Defaults to {@code versioned-voice-agent-java}.
  • + *
+ */ +public class VoiceAgentVersionsSample { + public static void main(String[] args) { + Configuration configuration = Configuration.getGlobalConfiguration(); + String endpoint = configuration.get("FOUNDRY_PROJECT_ENDPOINT"); + String model = configuration.get("FOUNDRY_VOICE_MODEL", "gpt-realtime"); + VoiceModelType modelType = VoiceModelType.fromString(configuration.get( + "FOUNDRY_VOICE_MODEL_TYPE", VoiceModelType.MANAGED.toString())); + String agentName = configuration.get("FOUNDRY_VOICE_AGENT_NAME", "versioned-voice-agent-java"); + + AgentsClient client = new AgentsClientBuilder() + .credential(new DefaultAzureCredentialBuilder().build()) + .endpoint(endpoint) + .allowPreview(true) + .buildAgentsClient(); + try { + AgentVersionDetails first = client.createAgentVersion(agentName, + new CreateAgentVersionInput(VoiceAgentSampleUtils.createDefinition(modelType, model, + "You are a helpful voice assistant."))); + AgentVersionDetails released = client.createAgentVersion(agentName, + new CreateAgentVersionInput(VoiceAgentSampleUtils.createDefinition(modelType, model, + "You are a helpful voice assistant. Greet the caller by name.")) + .setDescription("Added a personalized greeting.")); + AgentVersionDetails draft = client.createAgentVersion(agentName, + new CreateAgentVersionInput(VoiceAgentSampleUtils.createDefinition(modelType, model, + "You are an experimental voice assistant.")) + .setDescription("Candidate persona under review.") + .setDraft(true)); + System.out.printf("Created versions %s, %s and draft %s%n", + first.getVersion(), released.getVersion(), draft.getVersion()); + + System.out.println("Released versions:"); + for (AgentVersionDetails version : client.listAgentVersions(agentName)) { + System.out.printf(" %s (draft=%s)%n", version.getVersion(), version.isDraft()); + } + System.out.println("All versions including drafts:"); + for (AgentVersionDetails version : client.listAgentVersions(agentName, null, null, null, null, true)) { + System.out.printf(" %s (draft=%s)%n", version.getVersion(), version.isDraft()); + } + AgentVersionDetails fetched = client.getAgentVersionDetails(agentName, released.getVersion()); + System.out.println("Fetched version: " + fetched.getVersion()); + } finally { + client.deleteAgent(agentName); + } + } +} diff --git a/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/voice/VoiceAgentWithToolsSample.java b/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/voice/VoiceAgentWithToolsSample.java new file mode 100644 index 0000000000000..347924ebf6e28 --- /dev/null +++ b/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/voice/VoiceAgentWithToolsSample.java @@ -0,0 +1,96 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.ai.agents.voice; + +import com.azure.ai.agents.AgentsClient; +import com.azure.ai.agents.AgentsClientBuilder; +import com.azure.core.util.Configuration; +import com.azure.identity.DefaultAzureCredentialBuilder; +import com.azure.ai.agents.models.AgentVersionDetails; +import com.azure.ai.agents.models.CreateAgentVersionInput; +import com.azure.ai.agents.models.RealtimeAudioFormatsAudioPcm; +import com.azure.ai.agents.models.RealtimeAudioFormatsAudioPcmRate; +import com.azure.ai.agents.models.RealtimeFunctionToolParameters; +import com.azure.ai.agents.models.VoiceAgentAudioConfig; +import com.azure.ai.agents.models.VoiceAgentAudioInputConfig; +import com.azure.ai.agents.models.VoiceAgentAudioOutputConfig; +import com.azure.ai.agents.models.VoiceAgentDefinition; +import com.azure.ai.agents.models.VoiceAgentFunctionTool; +import com.azure.ai.agents.models.VoiceAgentInputTranscription; +import com.azure.ai.agents.models.VoiceAgentInputTranscriptionModel; +import com.azure.ai.agents.models.VoiceAgentServerVadTurnDetection; +import com.azure.ai.agents.models.VoiceAgentSystemTool; +import com.azure.ai.agents.models.VoiceAgentSystemToolName; +import com.azure.ai.agents.models.VoiceAgentTool; +import com.azure.ai.agents.models.VoiceOutputModality; +import com.azure.ai.agents.models.VoiceType; +import com.azure.ai.agents.models.VoiceModelType; + +import java.util.Arrays; +import java.util.Collections; + +/** + * Demonstrates a voice-agent definition with audio processing, transcription, and tools. + * + *

Before running the sample, set these environment variables:

+ *
    + *
  • {@code FOUNDRY_PROJECT_ENDPOINT} - The Azure AI Project endpoint.
  • + *
  • {@code FOUNDRY_VOICE_MODEL} - Optional. The voice model or deployment name. Defaults to {@code gpt-realtime}.
  • + *
  • {@code FOUNDRY_VOICE_MODEL_TYPE} - Optional. The voice model type. Defaults to {@code managed}.
  • + *
  • {@code FOUNDRY_VOICE_AGENT_NAME} - The voice agent name. Defaults to {@code voice-agent-with-tools-java}.
  • + *
+ */ +public class VoiceAgentWithToolsSample { + public static void main(String[] args) { + Configuration configuration = Configuration.getGlobalConfiguration(); + String endpoint = configuration.get("FOUNDRY_PROJECT_ENDPOINT"); + String model = configuration.get("FOUNDRY_VOICE_MODEL", "gpt-realtime"); + VoiceModelType modelType = VoiceModelType.fromString(configuration.get( + "FOUNDRY_VOICE_MODEL_TYPE", VoiceModelType.MANAGED.toString())); + String agentName = configuration.get("FOUNDRY_VOICE_AGENT_NAME", "voice-agent-with-tools-java"); + + AgentsClient client = new AgentsClientBuilder() + .credential(new DefaultAzureCredentialBuilder().build()) + .endpoint(endpoint) + .allowPreview(true) + .buildAgentsClient(); + + RealtimeAudioFormatsAudioPcm pcm = new RealtimeAudioFormatsAudioPcm() + .setRate(RealtimeAudioFormatsAudioPcmRate.TWO_FOUR_ZERO_ZERO_ZERO); + VoiceAgentAudioInputConfig input = new VoiceAgentAudioInputConfig() + .setFormat(pcm) + .setTurnDetection(new VoiceAgentServerVadTurnDetection() + .setThreshold(0.5) + .setPrefixPaddingMs(300L) + .setSilenceDurationMs(500L)) + .setTranscription(new VoiceAgentInputTranscription(VoiceAgentInputTranscriptionModel.WHISPER_1)); + VoiceAgentAudioOutputConfig output = new VoiceAgentAudioOutputConfig() + .setVoice("en-US-AvaNeural") + .setVoiceType(VoiceType.AZURE_STANDARD); + VoiceAgentFunctionTool weather = new VoiceAgentFunctionTool("get_weather") + .setDescription("Get the current weather for a city.") + .setParameters(new RealtimeFunctionToolParameters()); + VoiceAgentSystemTool endCall = new VoiceAgentSystemTool(VoiceAgentSystemToolName.END_CONVERSATION); + VoiceAgentDefinition definition = new VoiceAgentDefinition( + modelType, model) + .setInstructions("Use tools when they help answer the caller.") + .setAudio(new VoiceAgentAudioConfig().setInput(input).setOutput(output)) + .setOutputModalities(Collections.singletonList(VoiceOutputModality.AUDIO)) + .setTools(Arrays.asList(weather, endCall)) + .setStore(true); + + try { + AgentVersionDetails created = client.createAgentVersion(agentName, + new CreateAgentVersionInput(definition)); + AgentVersionDetails fetched = client.getAgentVersionDetails(agentName, created.getVersion()); + VoiceAgentDefinition fetchedDefinition = (VoiceAgentDefinition) fetched.getDefinition(); + System.out.println("Configured voice tools: " + fetchedDefinition.getTools().size()); + for (VoiceAgentTool tool : fetchedDefinition.getTools()) { + System.out.printf(" %s%n", tool.getType()); + } + } finally { + client.deleteAgent(agentName); + } + } +} diff --git a/sdk/ai/azure-ai-agents/src/test/java/com/azure/ai/agents/AgentOptimizationPollerTests.java b/sdk/ai/azure-ai-agents/src/test/java/com/azure/ai/agents/AgentOptimizationPollerTests.java new file mode 100644 index 0000000000000..26d2b6f62e4e2 --- /dev/null +++ b/sdk/ai/azure-ai-agents/src/test/java/com/azure/ai/agents/AgentOptimizationPollerTests.java @@ -0,0 +1,92 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.ai.agents; + +import com.azure.ai.agents.models.AgentOptimizationJob; +import com.azure.ai.agents.models.AgentOptimizationJobResult; +import com.azure.ai.agents.models.JobStatus; +import com.azure.core.http.HttpHeaderName; +import com.azure.core.http.HttpHeaders; +import com.azure.core.http.HttpMethod; +import com.azure.core.http.HttpRequest; +import com.azure.core.test.utils.MockTokenCredential; +import com.azure.core.util.polling.AsyncPollResponse; +import com.azure.core.util.polling.PollResponse; +import com.azure.core.util.polling.PollerFlux; +import com.azure.core.util.polling.SyncPoller; +import org.junit.jupiter.api.Test; +import reactor.test.StepVerifier; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class AgentOptimizationPollerTests { + private static final String ENDPOINT = "https://localhost:8080/api/projects/project"; + private static final HttpHeaderName OPERATION_LOCATION = HttpHeaderName.fromString("Operation-Location"); + private static final HttpHeaderName OPERATION_ID = HttpHeaderName.fromString("Operation-Id"); + + @Test + public void syncPollerExposesInitialJobIdAndOperationId() { + HttpHeaders headers + = new HttpHeaders().set(OPERATION_LOCATION, ENDPOINT + "/agent_optimization_jobs/optimization-job-sync"); + DeterministicHttpClient httpClient + = new DeterministicHttpClient().enqueueJson(201, headers, jobJson("optimization-job-sync", "queued")) + .enqueueJson(200, jobJson("optimization-job-sync", "queued")); + BetaAgentsClient client = createBuilder(httpClient).beta().buildBetaAgentsClient(); + + SyncPoller poller + = client.beginCreateOptimizationJob(new AgentOptimizationJob(), "operation-sync"); + PollResponse response = poller.poll(); + + assertNotNull(response.getValue()); + assertEquals("optimization-job-sync", response.getValue().getId()); + assertEquals(JobStatus.QUEUED, response.getValue().getStatus()); + assertEquals(2, httpClient.getRequests().size()); + HttpRequest request = httpClient.getRequest(0); + assertEquals(HttpMethod.POST, request.getHttpMethod()); + assertTrue(request.getUrl().getPath().endsWith("/agent_optimization_jobs")); + assertEquals("operation-sync", request.getHeaders().getValue(OPERATION_ID)); + } + + @Test + public void asyncPollerExposesInitialJobIdAndOperationId() { + HttpHeaders headers + = new HttpHeaders().set(OPERATION_LOCATION, ENDPOINT + "/agent_optimization_jobs/optimization-job-async"); + DeterministicHttpClient httpClient + = new DeterministicHttpClient().enqueueJson(201, headers, jobJson("optimization-job-async", "queued")) + .enqueueJson(200, jobJson("optimization-job-async", "queued")); + BetaAgentsAsyncClient client = createBuilder(httpClient).beta().buildBetaAgentsAsyncClient(); + + PollerFlux poller + = client.beginCreateOptimizationJob(new AgentOptimizationJob(), "operation-async"); + + StepVerifier.create(poller.next()) + .assertNext(response -> assertInitialAsyncResponse(response, "optimization-job-async")) + .verifyComplete(); + + assertEquals(2, httpClient.getRequests().size()); + HttpRequest request = httpClient.getRequest(0); + assertEquals(HttpMethod.POST, request.getHttpMethod()); + assertEquals("operation-async", request.getHeaders().getValue(OPERATION_ID)); + } + + private static void assertInitialAsyncResponse( + AsyncPollResponse response, String expectedJobId) { + assertNotNull(response.getValue()); + assertEquals(expectedJobId, response.getValue().getId()); + assertEquals(JobStatus.QUEUED, response.getValue().getStatus()); + } + + private static AgentsClientBuilder createBuilder(DeterministicHttpClient httpClient) { + return new AgentsClientBuilder().endpoint(ENDPOINT) + .credential(new MockTokenCredential()) + .httpClient(httpClient) + .serviceVersion(AgentsServiceVersion.V1); + } + + private static String jobJson(String jobId, String status) { + return "{\"id\":\"" + jobId + "\",\"status\":\"" + status + "\",\"created_at\":1,\"updated_at\":1}"; + } +} diff --git a/sdk/ai/azure-ai-agents/src/test/java/com/azure/ai/agents/AgentProtocolMockTests.java b/sdk/ai/azure-ai-agents/src/test/java/com/azure/ai/agents/AgentProtocolMockTests.java new file mode 100644 index 0000000000000..a1685bbc2f52a --- /dev/null +++ b/sdk/ai/azure-ai-agents/src/test/java/com/azure/ai/agents/AgentProtocolMockTests.java @@ -0,0 +1,217 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.ai.agents; + +import com.azure.ai.agents.models.AgentEndpointConfig; +import com.azure.ai.agents.models.AgentReference; +import com.azure.ai.agents.models.AzureCreateResponseOptions; +import com.azure.ai.agents.models.CreateAgentVersionInput; +import com.azure.ai.agents.models.FixedRatioVersionSelectionRule; +import com.azure.ai.agents.models.PromptAgentDefinition; +import com.azure.ai.agents.models.PromptAgentDefinitionTextOptions; +import com.azure.ai.agents.models.ProtocolConfiguration; +import com.azure.ai.agents.models.ResponseFormatJsonSchemaInner; +import com.azure.ai.agents.models.ResponsesProtocolConfiguration; +import com.azure.ai.agents.models.TextResponseFormatJsonSchema; +import com.azure.ai.agents.models.UpdateAgentDetailsOptions; +import com.azure.ai.agents.models.VersionSelector; +import com.azure.core.http.HttpMethod; +import com.azure.core.http.HttpRequest; +import com.azure.core.test.utils.MockTokenCredential; +import com.azure.core.util.BinaryData; +import com.openai.client.OpenAIClient; +import com.openai.client.OpenAIClientAsync; +import com.openai.models.responses.Response; +import com.openai.models.responses.ResponseCreateParams; +import org.junit.jupiter.api.Test; +import reactor.core.publisher.Mono; +import reactor.test.StepVerifier; + +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class AgentProtocolMockTests { + private static final String ENDPOINT = "https://localhost:8080/api/projects/project"; + private static final String AGENT_NAME = "protocol-agent-test"; + private static final String CALENDAR_JSON + = "{\"name\":\"Science fair\",\"date\":\"2025-11-07\",\"participants\":[\"Alice\",\"Bob\"]}"; + + @Test + public void syncStructuredOutputIsSentAndParsed() { + DeterministicHttpClient httpClient + = new DeterministicHttpClient().enqueueJson(200, promptVersionJson("1", true)) + .enqueueJson(200, responseJson("resp-structured-sync", CALENDAR_JSON)); + AgentsClientBuilder builder = createBuilder(httpClient); + AgentsClient agentsClient = builder.buildAgentsClient(); + ResponsesClient responsesClient = builder.buildResponsesClient(); + + agentsClient.createAgentVersion(AGENT_NAME, new CreateAgentVersionInput(createStructuredOutputDefinition())); + Response response = responsesClient.createAzureResponse( + new AzureCreateResponseOptions().setAgentReference(new AgentReference(AGENT_NAME).setVersion("1")), + ResponseCreateParams.builder().conversation("conv-structured-sync")); + + assertEquals(CALENDAR_JSON, responseText(response)); + String createAgentBody = body(httpClient.getRequest(0)); + assertTrue(createAgentBody.contains("\"name\":\"CalendarEvent\"")); + assertTrue(createAgentBody.contains("\"additionalProperties\":false")); + String responseBody = body(httpClient.getRequest(1)); + assertTrue(responseBody.contains("\"conversation\":\"conv-structured-sync\"")); + assertTrue(responseBody.contains("\"agent_reference\"")); + assertTrue(responseBody.contains("\"name\":\"" + AGENT_NAME + "\"")); + httpClient.assertResponsesConsumed(); + } + + @Test + public void asyncStructuredOutputIsSentAndParsed() { + DeterministicHttpClient httpClient + = new DeterministicHttpClient().enqueueJson(200, promptVersionJson("1", true)) + .enqueueJson(200, responseJson("resp-structured-async", CALENDAR_JSON)); + AgentsClientBuilder builder = createBuilder(httpClient); + AgentsAsyncClient agentsClient = builder.buildAgentsAsyncClient(); + ResponsesAsyncClient responsesClient = builder.buildResponsesAsyncClient(); + + Mono operation = agentsClient + .createAgentVersion(AGENT_NAME, new CreateAgentVersionInput(createStructuredOutputDefinition())) + .then(Mono.defer(() -> responsesClient.createAzureResponse( + new AzureCreateResponseOptions().setAgentReference(new AgentReference(AGENT_NAME).setVersion("1")), + ResponseCreateParams.builder().conversation("conv-structured-async")))); + + StepVerifier.create(operation) + .assertNext(response -> assertEquals(CALENDAR_JSON, responseText(response))) + .verifyComplete(); + assertTrue(body(httpClient.getRequest(0)).contains("\"name\":\"CalendarEvent\"")); + assertTrue(body(httpClient.getRequest(1)).contains("\"conversation\":\"conv-structured-async\"")); + httpClient.assertResponsesConsumed(); + } + + @Test + public void syncAgentEndpointRoutesResponsesProtocol() { + DeterministicHttpClient httpClient + = new DeterministicHttpClient().enqueueJson(200, promptVersionJson("2", false)) + .enqueueJson(200, agentJson("2")) + .enqueueJson(200, responseJson("resp-endpoint-sync", "routed response")); + AgentsClientBuilder builder = createBuilder(httpClient).allowPreview(true); + AgentsClient agentsClient = builder.buildAgentsClient(); + + agentsClient.createAgentVersion(AGENT_NAME, new CreateAgentVersionInput(new PromptAgentDefinition("gpt-4o"))); + agentsClient.updateAgentDetails(AGENT_NAME, endpointUpdate("2")); + OpenAIClient endpointClient = builder.buildAgentScopedOpenAIClient(AGENT_NAME); + Response response + = endpointClient.responses().create(ResponseCreateParams.builder().input("Route this request.").build()); + + assertEquals("routed response", responseText(response)); + HttpRequest patchRequest = httpClient.getRequest(1); + assertEquals(HttpMethod.PATCH, patchRequest.getHttpMethod()); + assertTrue(patchRequest.getUrl().getPath().endsWith("/agents/" + AGENT_NAME)); + String patchBody = body(patchRequest); + assertTrue(patchBody.contains("\"agent_version\":\"2\"")); + assertTrue(patchBody.contains("\"traffic_percentage\":100")); + assertTrue(patchBody.contains("\"responses\":{}")); + assertEndpointResponseRequest(httpClient.getRequest(2)); + httpClient.assertResponsesConsumed(); + } + + @Test + public void asyncAgentEndpointRoutesResponsesProtocol() { + DeterministicHttpClient httpClient + = new DeterministicHttpClient().enqueueJson(200, promptVersionJson("2", false)) + .enqueueJson(200, agentJson("2")) + .enqueueJson(200, responseJson("resp-endpoint-async", "async routed response")); + AgentsClientBuilder builder = createBuilder(httpClient).allowPreview(true); + AgentsAsyncClient agentsClient = builder.buildAgentsAsyncClient(); + OpenAIClientAsync endpointClient = builder.buildAgentScopedOpenAIAsyncClient(AGENT_NAME); + + Mono operation = agentsClient + .createAgentVersion(AGENT_NAME, new CreateAgentVersionInput(new PromptAgentDefinition("gpt-4o"))) + .then(agentsClient.updateAgentDetails(AGENT_NAME, endpointUpdate("2"))) + .then(Mono.defer(() -> Mono.fromFuture( + endpointClient.responses().create(ResponseCreateParams.builder().input("Route async.").build())))); + + StepVerifier.create(operation) + .assertNext(response -> assertEquals("async routed response", responseText(response))) + .verifyComplete(); + assertTrue(body(httpClient.getRequest(1)).contains("\"agent_version\":\"2\"")); + assertEndpointResponseRequest(httpClient.getRequest(2)); + httpClient.assertResponsesConsumed(); + } + + private static AgentsClientBuilder createBuilder(DeterministicHttpClient httpClient) { + return new AgentsClientBuilder().endpoint(ENDPOINT) + .credential(new MockTokenCredential()) + .httpClient(httpClient) + .serviceVersion(AgentsServiceVersion.V1); + } + + private static PromptAgentDefinition createStructuredOutputDefinition() { + Map properties = new LinkedHashMap<>(); + properties.put("name", Collections.singletonMap("type", "string")); + properties.put("date", Collections.singletonMap("type", "string")); + Map participants = new LinkedHashMap<>(); + participants.put("type", "array"); + participants.put("items", Collections.singletonMap("type", "string")); + properties.put("participants", participants); + Map schema = new LinkedHashMap<>(); + schema.put("type", "object"); + schema.put("properties", properties); + schema.put("required", Arrays.asList("name", "date", "participants")); + schema.put("additionalProperties", false); + ResponseFormatJsonSchemaInner schemaModel + = BinaryData.fromObject(schema).toObject(ResponseFormatJsonSchemaInner.class); + return new PromptAgentDefinition("gpt-4o").setInstructions("Extract a calendar event.") + .setText(new PromptAgentDefinitionTextOptions() + .setFormat(new TextResponseFormatJsonSchema("CalendarEvent", schemaModel).setStrict(true))); + } + + private static UpdateAgentDetailsOptions endpointUpdate(String version) { + AgentEndpointConfig endpoint = new AgentEndpointConfig() + .setVersionSelector(new VersionSelector().setVersionSelectionRules( + Collections.singletonList(new FixedRatioVersionSelectionRule(100).setAgentVersion(version)))) + .setProtocolConfiguration(new ProtocolConfiguration().setResponses(new ResponsesProtocolConfiguration())); + return new UpdateAgentDetailsOptions().setAgentEndpoint(endpoint); + } + + private static void assertEndpointResponseRequest(HttpRequest request) { + assertEquals(HttpMethod.POST, request.getHttpMethod()); + assertTrue( + request.getUrl().getPath().endsWith("/agents/" + AGENT_NAME + "/endpoint/protocols/openai/responses")); + assertTrue(body(request).contains("Route")); + } + + private static String responseText(Response response) { + return response.output().get(0).asMessage().content().get(0).asOutputText().text(); + } + + private static String body(HttpRequest request) { + return request.getBodyAsBinaryData().toString(); + } + + private static String promptVersionJson(String version, boolean structuredOutput) { + String text = structuredOutput + ? ",\"text\":{\"format\":{\"type\":\"json_schema\",\"name\":\"CalendarEvent\"," + + "\"schema\":{\"type\":\"object\"},\"strict\":true}}" + : ""; + return "{\"object\":\"agent.version\",\"id\":\"agent-" + version + "\",\"name\":\"" + AGENT_NAME + + "\",\"version\":\"" + version + "\",\"created_at\":1,\"metadata\":{}," + + "\"definition\":{\"kind\":\"prompt\",\"model\":\"gpt-4o\"" + text + "}}"; + } + + private static String agentJson(String latestVersion) { + return "{\"object\":\"agent\",\"id\":\"agent-id\",\"name\":\"" + AGENT_NAME + + "\",\"state\":\"enabled\",\"versions\":{\"latest\":" + promptVersionJson(latestVersion, false) + "}}"; + } + + static String responseJson(String id, String text) { + return "{\"id\":\"" + id + "\",\"object\":\"response\",\"created_at\":1," + + "\"model\":\"gpt-4o\",\"status\":\"completed\",\"parallel_tool_calls\":true," + + "\"tool_choice\":\"auto\",\"tools\":[],\"output\":[{\"id\":\"msg-1\"," + + "\"type\":\"message\",\"role\":\"assistant\",\"status\":\"completed\"," + + "\"content\":[{\"type\":\"output_text\",\"text\":" + BinaryData.fromObject(text) + + ",\"annotations\":[]}]}]}"; + } +} diff --git a/sdk/ai/azure-ai-agents/src/test/java/com/azure/ai/agents/DeterministicHttpClient.java b/sdk/ai/azure-ai-agents/src/test/java/com/azure/ai/agents/DeterministicHttpClient.java new file mode 100644 index 0000000000000..d8f3069fd177c --- /dev/null +++ b/sdk/ai/azure-ai-agents/src/test/java/com/azure/ai/agents/DeterministicHttpClient.java @@ -0,0 +1,79 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.ai.agents; + +import com.azure.core.http.HttpClient; +import com.azure.core.http.HttpHeaderName; +import com.azure.core.http.HttpHeaders; +import com.azure.core.http.HttpRequest; +import com.azure.core.http.HttpResponse; +import com.azure.core.test.http.MockHttpResponse; +import com.azure.core.util.Context; +import reactor.core.publisher.Mono; + +import java.nio.charset.StandardCharsets; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Deque; +import java.util.List; +import java.util.function.Function; + +final class DeterministicHttpClient implements HttpClient { + private final Deque> responses = new ArrayDeque<>(); + private final List requests = new ArrayList<>(); + + DeterministicHttpClient enqueueJson(int statusCode, String body) { + return enqueue(statusCode, new HttpHeaders().set(HttpHeaderName.CONTENT_TYPE, "application/json"), + body.getBytes(StandardCharsets.UTF_8)); + } + + DeterministicHttpClient enqueueJson(int statusCode, HttpHeaders headers, String body) { + HttpHeaders responseHeaders = new HttpHeaders(headers).set(HttpHeaderName.CONTENT_TYPE, "application/json"); + return enqueue(statusCode, responseHeaders, body.getBytes(StandardCharsets.UTF_8)); + } + + DeterministicHttpClient enqueueSse(String body) { + return enqueue(200, new HttpHeaders().set(HttpHeaderName.CONTENT_TYPE, "text/event-stream"), + body.getBytes(StandardCharsets.UTF_8)); + } + + DeterministicHttpClient enqueue(int statusCode, HttpHeaders headers, byte[] body) { + return enqueueResponse(request -> new MockHttpResponse(request, statusCode, headers, body)); + } + + DeterministicHttpClient enqueueResponse(Function responseFactory) { + responses.addLast(responseFactory); + return this; + } + + List getRequests() { + return Collections.unmodifiableList(requests); + } + + HttpRequest getRequest(int index) { + return requests.get(index); + } + + void assertResponsesConsumed() { + if (!responses.isEmpty()) { + throw new AssertionError(responses.size() + " deterministic response(s) were not consumed"); + } + } + + @Override + public Mono send(HttpRequest request) { + requests.add(request); + Function responseFactory = responses.pollFirst(); + if (responseFactory == null) { + return Mono.error(new IllegalStateException("No deterministic response queued for " + request.getUrl())); + } + return Mono.just(responseFactory.apply(request)); + } + + @Override + public Mono send(HttpRequest request, Context context) { + return send(request); + } +} diff --git a/sdk/ai/azure-ai-agents/src/test/java/com/azure/ai/agents/FoundryFeaturesHeaderVerificationTest.java b/sdk/ai/azure-ai-agents/src/test/java/com/azure/ai/agents/FoundryFeaturesHeaderVerificationTest.java index fefb6ebd6d5b8..a2c167eee810c 100644 --- a/sdk/ai/azure-ai-agents/src/test/java/com/azure/ai/agents/FoundryFeaturesHeaderVerificationTest.java +++ b/sdk/ai/azure-ai-agents/src/test/java/com/azure/ai/agents/FoundryFeaturesHeaderVerificationTest.java @@ -5,6 +5,9 @@ import com.azure.ai.agents.implementation.models.AgentDefinitionOptInKeys; import com.azure.ai.agents.implementation.models.FoundryFeaturesOptInKeys; +import com.azure.ai.agents.models.CreateAgentVersionInput; +import com.azure.ai.agents.models.WorkflowAgentDefinition; +import com.azure.core.exception.HttpResponseException; import com.azure.core.http.HttpClient; import com.azure.core.http.HttpHeaderName; import com.azure.core.http.HttpHeaders; @@ -22,6 +25,7 @@ import com.azure.core.util.Context; import org.junit.jupiter.api.Test; import reactor.core.publisher.Mono; +import reactor.test.StepVerifier; import java.nio.charset.StandardCharsets; import java.util.ArrayList; @@ -32,7 +36,10 @@ import java.util.stream.Stream; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; public class FoundryFeaturesHeaderVerificationTest { private static final HttpHeaderName FOUNDRY_FEATURES = HttpHeaderName.fromString("Foundry-Features"); @@ -115,6 +122,39 @@ public void allowPreviewFalseDoesNotAddGaAgentHeader() { assertNull(foundryFeatures(httpClient)); } + @Test + public void workflowWithoutPreviewPropagatesForbiddenResponse() { + RecordingHttpClient httpClient + = new RecordingHttpClient(FoundryFeaturesHeaderVerificationTest::forbiddenResponse); + AgentsClient client = createBuilder(httpClient).buildAgentsClient(); + + HttpResponseException exception + = assertThrows(HttpResponseException.class, () -> client.createAgentVersion("workflow-agent-preview-test", + new CreateAgentVersionInput(new WorkflowAgentDefinition().setWorkflow(minimalWorkflow())))); + + assertEquals(403, exception.getResponse().getStatusCode()); + assertTrue(exception.getMessage().contains("PreviewFeatureRequired")); + assertNull(foundryFeatures(httpClient)); + } + + @Test + public void asyncWorkflowWithoutPreviewPropagatesForbiddenResponse() { + RecordingHttpClient httpClient + = new RecordingHttpClient(FoundryFeaturesHeaderVerificationTest::forbiddenResponse); + AgentsAsyncClient client = createBuilder(httpClient).buildAgentsAsyncClient(); + + StepVerifier + .create(client.createAgentVersion("workflow-agent-preview-test", + new CreateAgentVersionInput(new WorkflowAgentDefinition().setWorkflow(minimalWorkflow())))) + .expectErrorSatisfies(error -> { + HttpResponseException exception = assertInstanceOf(HttpResponseException.class, error); + assertEquals(403, exception.getResponse().getStatusCode()); + assertTrue(exception.getMessage().contains("PreviewFeatureRequired")); + }) + .verify(); + assertNull(foundryFeatures(httpClient)); + } + @Test public void allowPreviewUsesBuiltClientFeatureHeaderWithoutPathMatching() { RecordingHttpClient httpClient = new RecordingHttpClient(); @@ -242,6 +282,18 @@ private static HttpResponse openAIResponse(HttpRequest request) { return jsonResponse(request, responseBody); } + private static HttpResponse forbiddenResponse(HttpRequest request) { + String responseBody = "{\"error\":{\"code\":\"PreviewFeatureRequired\"," + + "\"message\":\"The requested agent kind requires preview opt-in.\"}}"; + HttpHeaders responseHeaders + = new HttpHeaders().set(HttpHeaderName.fromString("Content-Type"), "application/json"); + return new MockHttpResponse(request, 403, responseHeaders, responseBody.getBytes(StandardCharsets.UTF_8)); + } + + private static String minimalWorkflow() { + return "kind: workflow\ntrigger:\n kind: OnConversationStart\n id: my_workflow\n actions: []\n"; + } + private static HttpResponse jsonResponse(HttpRequest request, String responseBody) { HttpHeaders responseHeaders = new HttpHeaders().set(HttpHeaderName.fromString("Content-Type"), "application/json"); diff --git a/sdk/ai/azure-ai-agents/src/test/java/com/azure/ai/agents/MultiToolBehaviorMockTests.java b/sdk/ai/azure-ai-agents/src/test/java/com/azure/ai/agents/MultiToolBehaviorMockTests.java new file mode 100644 index 0000000000000..33bdeeb86443b --- /dev/null +++ b/sdk/ai/azure-ai-agents/src/test/java/com/azure/ai/agents/MultiToolBehaviorMockTests.java @@ -0,0 +1,240 @@ +// 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.AutoCodeInterpreterToolParameter; +import com.azure.ai.agents.models.AzureCreateResponseOptions; +import com.azure.ai.agents.models.CodeInterpreterTool; +import com.azure.ai.agents.models.CreateAgentVersionInput; +import com.azure.ai.agents.models.FileSearchTool; +import com.azure.ai.agents.models.FunctionTool; +import com.azure.ai.agents.models.PromptAgentDefinition; +import com.azure.ai.agents.models.Tool; +import com.azure.core.http.HttpRequest; +import com.azure.core.test.utils.MockTokenCredential; +import com.azure.core.util.BinaryData; +import com.openai.models.responses.Response; +import com.openai.models.responses.ResponseCreateParams; +import com.openai.models.responses.ResponseInputItem; +import org.junit.jupiter.api.Test; + +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class MultiToolBehaviorMockTests { + private static final String ENDPOINT = "https://localhost:8080/api/projects/project"; + private static final String AGENT_NAME = "multitool-agent"; + + @Test + public void codeInterpreterAndFunctionPreserveOrderAndContinuation() { + String outputs = codeInterpreterCall("code-1") + "," + + OpenAIResponseFixtures.functionCall("function-1", "save-call", "save_result", "{\"result\":\"83521\"}"); + DeterministicHttpClient httpClient + = new DeterministicHttpClient().enqueueJson(200, OpenAIResponseFixtures.promptAgentVersion(AGENT_NAME, "1")) + .enqueueJson(200, OpenAIResponseFixtures.response("resp-code-function", outputs)) + .enqueueJson(200, OpenAIResponseFixtures.response("resp-code-function-final", + OpenAIResponseFixtures.message("message-final", "Result saved."))); + AgentsClientBuilder builder = createBuilder(httpClient); + + builder.buildAgentsClient() + .createAgentVersion(AGENT_NAME, + definition(new CodeInterpreterTool().setContainer(new AutoCodeInterpreterToolParameter()), + functionTool("save_result"))); + ResponsesClient responsesClient = builder.buildResponsesClient(); + Response initial = responsesClient.createAzureResponse(agentOptions(), + ResponseCreateParams.builder().input("Calculate and save.")); + ResponseInputItem functionOutput + = ResponseInputItem.ofFunctionCallOutput(ResponseInputItem.FunctionCallOutput.builder() + .callId(initial.output().get(1).asFunctionCall().callId()) + .output("{\"saved\":true}") + .build()); + Response completed = responsesClient.createAzureResponse(agentOptions(), + ResponseCreateParams.builder() + .inputOfResponse(Collections.singletonList(functionOutput)) + .previousResponseId(initial.id())); + + assertToolOrder(httpClient.getRequest(0), "code_interpreter", "function"); + assertTrue(initial.output().get(0).isCodeInterpreterCall()); + assertEquals("container-1", initial.output().get(0).asCodeInterpreterCall().containerId()); + assertEquals("save_result", initial.output().get(1).asFunctionCall().name()); + assertEquals("Result saved.", responseText(completed)); + String continuation = body(httpClient.getRequest(2)); + assertTrue(continuation.contains("\"previous_response_id\":\"resp-code-function\"")); + assertTrue(continuation.contains("\"call_id\":\"save-call\"")); + httpClient.assertResponsesConsumed(); + } + + @Test + public void fileSearchAndCodeInterpreterParseBothToolCalls() { + String outputs = fileSearchCall("search-1") + "," + codeInterpreterCall("code-1") + "," + + OpenAIResponseFixtures.message("message-analysis", "Analysis complete."); + DeterministicHttpClient httpClient + = new DeterministicHttpClient().enqueueJson(200, OpenAIResponseFixtures.promptAgentVersion(AGENT_NAME, "1")) + .enqueueJson(200, OpenAIResponseFixtures.response("resp-search-code", outputs)); + AgentsClientBuilder builder = createBuilder(httpClient); + + builder.buildAgentsClient() + .createAgentVersion(AGENT_NAME, definition(new FileSearchTool(Collections.singletonList("vector-store-1")), + new CodeInterpreterTool().setContainer(new AutoCodeInterpreterToolParameter()))); + Response response = builder.buildResponsesClient() + .createAzureResponse(agentOptions(), ResponseCreateParams.builder().input("Find and analyze.")); + + assertToolOrder(httpClient.getRequest(0), "file_search", "code_interpreter"); + assertTrue(body(httpClient.getRequest(0)).contains("\"vector_store_ids\":[\"vector-store-1\"]")); + assertTrue(response.output().get(0).isFileSearchCall()); + assertEquals("sales data", response.output().get(0).asFileSearchCall().queries().get(0)); + assertTrue(response.output().get(1).isCodeInterpreterCall()); + assertEquals("Analysis complete.", responseText(response, 2)); + httpClient.assertResponsesConsumed(); + } + + @Test + public void fileSearchAndFunctionShareConversationState() { + String firstOutputs + = fileSearchCall("search-1") + "," + OpenAIResponseFixtures.message("message-search", "Q1 revenue found."); + String functionCall = OpenAIResponseFixtures.functionCall("function-save", "save-report-call", "save_report", + "{\"title\":\"Q1 report\",\"summary\":\"Revenue summary\"}"); + DeterministicHttpClient httpClient + = new DeterministicHttpClient().enqueueJson(200, OpenAIResponseFixtures.promptAgentVersion(AGENT_NAME, "1")) + .enqueueJson(200, OpenAIResponseFixtures.response("resp-search", firstOutputs)) + .enqueueJson(200, OpenAIResponseFixtures.response("resp-save", functionCall)) + .enqueueJson(200, OpenAIResponseFixtures.response("resp-saved", + OpenAIResponseFixtures.message("message-saved", "Report saved."))); + AgentsClientBuilder builder = createBuilder(httpClient); + + builder.buildAgentsClient() + .createAgentVersion(AGENT_NAME, definition(new FileSearchTool(Collections.singletonList("vector-store-1")), + functionTool("save_report"))); + ResponsesClient responsesClient = builder.buildResponsesClient(); + Response search = responsesClient.createAzureResponse(agentOptions(), + ResponseCreateParams.builder().conversation("conversation-1").input("Find Q1 revenue.")); + Response save = responsesClient.createAzureResponse(agentOptions(), + ResponseCreateParams.builder().conversation("conversation-1").input("Save the report.")); + ResponseInputItem output = ResponseInputItem.ofFunctionCallOutput(ResponseInputItem.FunctionCallOutput.builder() + .callId(save.output().get(0).asFunctionCall().callId()) + .output("{\"status\":\"saved\"}") + .build()); + Response completed = responsesClient.createAzureResponse(agentOptions(), + ResponseCreateParams.builder() + .conversation("conversation-1") + .inputOfResponse(Collections.singletonList(output)) + .previousResponseId(save.id())); + + assertToolOrder(httpClient.getRequest(0), "file_search", "function"); + assertTrue(search.output().get(0).isFileSearchCall()); + assertEquals("save_report", save.output().get(0).asFunctionCall().name()); + assertEquals("Report saved.", responseText(completed)); + assertTrue(body(httpClient.getRequest(1)).contains("\"conversation\":\"conversation-1\"")); + assertTrue(body(httpClient.getRequest(2)).contains("\"conversation\":\"conversation-1\"")); + String continuation = body(httpClient.getRequest(3)); + assertTrue(continuation.contains("\"conversation\":\"conversation-1\"")); + assertTrue(continuation.contains("\"previous_response_id\":\"resp-save\"")); + assertTrue(continuation.contains("\"call_id\":\"save-report-call\"")); + httpClient.assertResponsesConsumed(); + } + + @Test + public void allThreeToolsRoundTripInOneResponse() { + String outputs = fileSearchCall("search-1") + "," + codeInterpreterCall("code-1") + "," + OpenAIResponseFixtures + .functionCall("function-save", "analysis-call", "save_analysis", "{\"total\":520,\"average\":52}"); + DeterministicHttpClient httpClient + = new DeterministicHttpClient().enqueueJson(200, OpenAIResponseFixtures.promptAgentVersion(AGENT_NAME, "1")) + .enqueueJson(200, OpenAIResponseFixtures.response("resp-all-tools", outputs)) + .enqueueJson(200, OpenAIResponseFixtures.response("resp-all-tools-final", + OpenAIResponseFixtures.message("message-final", "Analysis saved."))); + AgentsClientBuilder builder = createBuilder(httpClient); + + builder.buildAgentsClient() + .createAgentVersion(AGENT_NAME, + definition(new FileSearchTool(Collections.singletonList("vector-store-1")), + new CodeInterpreterTool().setContainer(new AutoCodeInterpreterToolParameter()), + functionTool("save_analysis"))); + ResponsesClient responsesClient = builder.buildResponsesClient(); + Response initial = responsesClient.createAzureResponse(agentOptions(), + ResponseCreateParams.builder().input("Find, analyze, and save.")); + ResponseInputItem output = ResponseInputItem.ofFunctionCallOutput(ResponseInputItem.FunctionCallOutput.builder() + .callId(initial.output().get(2).asFunctionCall().callId()) + .output("{\"saved\":true}") + .build()); + Response completed = responsesClient.createAzureResponse(agentOptions(), + ResponseCreateParams.builder() + .inputOfResponse(Collections.singletonList(output)) + .previousResponseId(initial.id())); + + assertToolOrder(httpClient.getRequest(0), "file_search", "code_interpreter", "function"); + String agentBody = body(httpClient.getRequest(0)); + assertTrue(agentBody.contains("\"strict\":true")); + assertTrue(agentBody.contains("\"additionalProperties\":false")); + assertTrue(initial.output().get(0).isFileSearchCall()); + assertTrue(initial.output().get(1).isCodeInterpreterCall()); + assertTrue(initial.output().get(2).isFunctionCall()); + assertEquals("Analysis saved.", responseText(completed)); + assertTrue(body(httpClient.getRequest(2)).contains("\"previous_response_id\":\"resp-all-tools\"")); + httpClient.assertResponsesConsumed(); + } + + private static AgentsClientBuilder createBuilder(DeterministicHttpClient httpClient) { + return new AgentsClientBuilder().endpoint(ENDPOINT) + .credential(new MockTokenCredential()) + .httpClient(httpClient) + .serviceVersion(AgentsServiceVersion.V1); + } + + private static AzureCreateResponseOptions agentOptions() { + return new AzureCreateResponseOptions().setAgentReference(new AgentReference(AGENT_NAME).setVersion("1")); + } + + private static CreateAgentVersionInput definition(Tool... tools) { + return new CreateAgentVersionInput(new PromptAgentDefinition("gpt-4o").setTools(Arrays.asList(tools))); + } + + private static FunctionTool functionTool(String name) { + Map properties = new LinkedHashMap<>(); + properties.put("value", Collections.singletonMap("type", "string")); + Map parameters = new LinkedHashMap<>(); + parameters.put("type", BinaryData.fromObject("object")); + parameters.put("properties", BinaryData.fromObject(properties)); + parameters.put("required", BinaryData.fromObject(Collections.singletonList("value"))); + parameters.put("additionalProperties", BinaryData.fromObject(false)); + return new FunctionTool(name, parameters, true); + } + + private static String fileSearchCall(String id) { + return "{\"id\":\"" + id + "\",\"type\":\"file_search_call\"," + + "\"queries\":[\"sales data\"],\"status\":\"completed\",\"results\":[]}"; + } + + private static String codeInterpreterCall(String id) { + return "{\"id\":\"" + id + "\",\"type\":\"code_interpreter_call\"," + + "\"container_id\":\"container-1\",\"code\":\"print(52)\"," + "\"outputs\":[],\"status\":\"completed\"}"; + } + + private static void assertToolOrder(HttpRequest request, String... toolTypes) { + String requestBody = body(request); + int previous = -1; + for (String toolType : toolTypes) { + int current = requestBody.indexOf("\"type\":\"" + toolType + "\""); + assertTrue(current > previous, "Expected tool order to contain " + toolType); + previous = current; + } + } + + private static String responseText(Response response) { + return responseText(response, 0); + } + + private static String responseText(Response response, int itemIndex) { + return response.output().get(itemIndex).asMessage().content().get(0).asOutputText().text(); + } + + private static String body(HttpRequest request) { + return request.getBodyAsBinaryData().toString(); + } +} diff --git a/sdk/ai/azure-ai-agents/src/test/java/com/azure/ai/agents/OpenAIResponseFixtures.java b/sdk/ai/azure-ai-agents/src/test/java/com/azure/ai/agents/OpenAIResponseFixtures.java new file mode 100644 index 0000000000000..6740fa0b8e742 --- /dev/null +++ b/sdk/ai/azure-ai-agents/src/test/java/com/azure/ai/agents/OpenAIResponseFixtures.java @@ -0,0 +1,38 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.ai.agents; + +import com.azure.core.util.BinaryData; + +final class OpenAIResponseFixtures { + private OpenAIResponseFixtures() { + } + + static String response(String id, String outputItems) { + return "{\"id\":\"" + id + "\",\"object\":\"response\",\"created_at\":1," + + "\"model\":\"gpt-4o\",\"status\":\"completed\",\"parallel_tool_calls\":true," + + "\"tool_choice\":\"auto\",\"tools\":[],\"output\":[" + outputItems + "]}"; + } + + static String message(String id, String text) { + return message(id, text, ""); + } + + static String message(String id, String text, String annotations) { + return "{\"id\":\"" + id + "\",\"type\":\"message\",\"role\":\"assistant\"," + + "\"status\":\"completed\",\"content\":[{\"type\":\"output_text\",\"text\":" + BinaryData.fromObject(text) + + ",\"annotations\":[" + annotations + "]}]}"; + } + + static String functionCall(String id, String callId, String name, String arguments) { + return "{\"id\":\"" + id + "\",\"type\":\"function_call\",\"call_id\":\"" + callId + "\",\"name\":\"" + name + + "\",\"arguments\":" + BinaryData.fromObject(arguments) + ",\"status\":\"completed\"}"; + } + + static String promptAgentVersion(String agentName, String version) { + return "{\"object\":\"agent.version\",\"id\":\"agent-" + version + "\",\"name\":\"" + agentName + + "\",\"version\":\"" + version + "\",\"created_at\":1,\"metadata\":{}," + + "\"definition\":{\"kind\":\"prompt\",\"model\":\"gpt-4o\",\"tools\":[]}}"; + } +} diff --git a/sdk/ai/azure-ai-agents/src/test/java/com/azure/ai/agents/PortableToolBehaviorMockTests.java b/sdk/ai/azure-ai-agents/src/test/java/com/azure/ai/agents/PortableToolBehaviorMockTests.java new file mode 100644 index 0000000000000..f63ede9d6596a --- /dev/null +++ b/sdk/ai/azure-ai-agents/src/test/java/com/azure/ai/agents/PortableToolBehaviorMockTests.java @@ -0,0 +1,382 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.ai.agents; + +import com.azure.ai.agents.models.AISearchIndexResource; +import com.azure.ai.agents.models.AgentReference; +import com.azure.ai.agents.models.AzureAISearchQueryType; +import com.azure.ai.agents.models.AzureAISearchTool; +import com.azure.ai.agents.models.AzureAISearchToolResource; +import com.azure.ai.agents.models.AzureCreateResponseOptions; +import com.azure.ai.agents.models.BingGroundingSearchConfiguration; +import com.azure.ai.agents.models.BingGroundingSearchToolParameters; +import com.azure.ai.agents.models.BingGroundingTool; +import com.azure.ai.agents.models.CreateAgentVersionInput; +import com.azure.ai.agents.models.ImageGenTool; +import com.azure.ai.agents.models.ImageGenToolModel; +import com.azure.ai.agents.models.ImageGenToolQuality; +import com.azure.ai.agents.models.ImageGenToolSize; +import com.azure.ai.agents.models.McpTool; +import com.azure.ai.agents.models.MemorySearchPreviewTool; +import com.azure.ai.agents.models.OpenApiFunctionDefinition; +import com.azure.ai.agents.models.OpenApiProjectConnectionAuthDetails; +import com.azure.ai.agents.models.OpenApiProjectConnectionSecurityScheme; +import com.azure.ai.agents.models.OpenApiTool; +import com.azure.ai.agents.models.PromptAgentDefinition; +import com.azure.ai.agents.models.Tool; +import com.azure.core.http.HttpHeaderName; +import com.azure.core.http.HttpHeaders; +import com.azure.core.http.HttpMethod; +import com.azure.core.http.HttpRequest; +import com.azure.core.test.utils.MockTokenCredential; +import com.azure.core.util.BinaryData; +import com.openai.client.OpenAIClient; +import com.openai.core.http.HttpResponse; +import com.openai.models.containers.files.content.ContentRetrieveParams; +import com.openai.models.responses.Response; +import com.openai.models.responses.ResponseCreateParams; +import com.openai.models.responses.ResponseInputItem; +import com.openai.models.responses.ResponseOutputItem; +import com.openai.models.responses.ResponseOutputText; +import org.junit.jupiter.api.Test; +import reactor.core.publisher.Mono; +import reactor.test.StepVerifier; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Base64; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class PortableToolBehaviorMockTests { + private static final String ENDPOINT = "https://localhost:8080/api/projects/project"; + private static final String AGENT_NAME = "portable-tool-agent"; + + @Test + public void codeInterpreterGeneratedFileCitationCanBeDownloaded() throws IOException { + byte[] png = new byte[] { (byte) 0x89, 0x50, 0x4E, 0x47, 0x01 }; + String citation = "{\"type\":\"container_file_citation\",\"container_id\":\"container-1\"," + + "\"file_id\":\"file-1\",\"filename\":\"chart.png\",\"start_index\":0,\"end_index\":9}"; + String responseJson = OpenAIResponseFixtures.response("resp-file", + OpenAIResponseFixtures.message("msg-file", "chart.png", citation)); + DeterministicHttpClient httpClient = new DeterministicHttpClient().enqueueJson(200, responseJson) + .enqueue(200, new HttpHeaders().set(HttpHeaderName.CONTENT_TYPE, "image/png"), png); + AgentsClientBuilder builder = createBuilder(httpClient); + + Response response = builder.buildResponsesClient() + .createAzureResponse(agentOptions(), ResponseCreateParams.builder().input("Create a chart.")); + ResponseOutputText.Annotation.ContainerFileCitation parsedCitation = response.output() + .get(0) + .asMessage() + .content() + .get(0) + .asOutputText() + .annotations() + .get(0) + .asContainerFileCitation(); + assertEquals("container-1", parsedCitation.containerId()); + assertEquals("file-1", parsedCitation.fileId()); + assertEquals("chart.png", parsedCitation.filename()); + + OpenAIClient openAIClient = builder.buildOpenAIClient(); + ContentRetrieveParams params = ContentRetrieveParams.builder() + .containerId(parsedCitation.containerId()) + .fileId(parsedCitation.fileId()) + .build(); + byte[] downloaded; + try (HttpResponse content = openAIClient.containers().files().content().retrieve(params)) { + downloaded = readBytes(content.body()); + } + + assertArrayEquals(png, downloaded); + HttpRequest download = httpClient.getRequest(1); + assertEquals(HttpMethod.GET, download.getHttpMethod()); + assertTrue(download.getUrl().getPath().endsWith("/containers/container-1/files/file-1/content")); + httpClient.assertResponsesConsumed(); + } + + @Test + public void multipleFunctionCallsAreReturnedWithMatchingOutputs() { + String calls = OpenAIResponseFixtures.functionCall("call-item-1", "call-1", "get_weather", + "{\"city\":\"New York\"}") + "," + + OpenAIResponseFixtures.functionCall("call-item-2", "call-2", "get_forecast", "{\"city\":\"New York\"}"); + DeterministicHttpClient httpClient + = new DeterministicHttpClient().enqueueJson(200, OpenAIResponseFixtures.response("resp-calls", calls)) + .enqueueJson(200, OpenAIResponseFixtures.response("resp-final", + OpenAIResponseFixtures.message("msg-final", "Weather and forecast received."))); + ResponsesClient responsesClient = createBuilder(httpClient).buildResponsesClient(); + + Response initial + = responsesClient.createAzureResponse(agentOptions(), ResponseCreateParams.builder().input("Weather?")); + List outputs = new ArrayList<>(); + for (ResponseOutputItem item : initial.output()) { + assertTrue(item.isFunctionCall()); + outputs.add(ResponseInputItem.ofFunctionCallOutput(ResponseInputItem.FunctionCallOutput.builder() + .callId(item.asFunctionCall().callId()) + .output("{\"ok\":true}") + .build())); + } + Response completed = responsesClient.createAzureResponse(agentOptions(), + ResponseCreateParams.builder().inputOfResponse(outputs).previousResponseId(initial.id())); + + assertEquals(2, outputs.size()); + assertEquals("Weather and forecast received.", responseText(completed)); + String continuation = body(httpClient.getRequest(1)); + assertTrue(continuation.contains("\"previous_response_id\":\"resp-calls\"")); + assertTrue(continuation.contains("\"call_id\":\"call-1\"")); + assertTrue(continuation.contains("\"call_id\":\"call-2\"")); + assertTrue(continuation.contains("\"type\":\"function_call_output\"")); + httpClient.assertResponsesConsumed(); + } + + @Test + public void asyncFunctionContinuationPreservesPreviousResponse() { + String call = OpenAIResponseFixtures.functionCall("call-item", "call-async", "get_temperature", + "{\"city\":\"Boston\"}"); + DeterministicHttpClient httpClient + = new DeterministicHttpClient().enqueueJson(200, OpenAIResponseFixtures.response("resp-async-call", call)) + .enqueueJson(200, OpenAIResponseFixtures.response("resp-async-final", + OpenAIResponseFixtures.message("msg-async", "72 degrees."))); + ResponsesAsyncClient responsesClient = createBuilder(httpClient).buildResponsesAsyncClient(); + + Mono operation + = responsesClient.createAzureResponse(agentOptions(), ResponseCreateParams.builder().input("Temperature?")) + .flatMap(initial -> { + ResponseInputItem output + = ResponseInputItem.ofFunctionCallOutput(ResponseInputItem.FunctionCallOutput.builder() + .callId(initial.output().get(0).asFunctionCall().callId()) + .output("{\"temperature\":72}") + .build()); + return responsesClient.createAzureResponse(agentOptions(), + ResponseCreateParams.builder() + .inputOfResponse(Collections.singletonList(output)) + .previousResponseId(initial.id())); + }); + + StepVerifier.create(operation) + .assertNext(response -> assertEquals("72 degrees.", responseText(response))) + .verifyComplete(); + assertTrue(body(httpClient.getRequest(1)).contains("\"previous_response_id\":\"resp-async-call\"")); + assertTrue(body(httpClient.getRequest(1)).contains("\"call_id\":\"call-async\"")); + httpClient.assertResponsesConsumed(); + } + + @Test + public void imageGenerationToolAndResultUseExpectedWireShape() { + byte[] png = new byte[] { (byte) 0x89, 0x50, 0x4E, 0x47 }; + String base64 = Base64.getEncoder().encodeToString(png); + String imageOutput = "{\"id\":\"image-1\",\"type\":\"image_generation_call\"," + + "\"status\":\"completed\",\"result\":\"" + base64 + "\"}"; + DeterministicHttpClient httpClient + = new DeterministicHttpClient().enqueueJson(200, OpenAIResponseFixtures.promptAgentVersion(AGENT_NAME, "1")) + .enqueueJson(200, OpenAIResponseFixtures.response("resp-image", imageOutput)); + AgentsClientBuilder builder = createBuilder(httpClient); + ImageGenTool imageTool = new ImageGenTool().setModel(ImageGenToolModel.GPT_IMAGE_1) + .setQuality(ImageGenToolQuality.LOW) + .setSize(ImageGenToolSize.fromString("1024x1024")); + + builder.buildAgentsClient() + .createAgentVersion(AGENT_NAME, new CreateAgentVersionInput( + new PromptAgentDefinition("gpt-4o").setTools(Collections.singletonList(imageTool)))); + Response response = builder.buildResponsesClient() + .createAzureResponse(agentOptions(), ResponseCreateParams.builder().input("Generate an image.")); + + String request = body(httpClient.getRequest(0)); + assertTrue(request.contains("\"type\":\"image_generation\"")); + assertTrue(request.contains("\"model\":\"gpt-image-1\"")); + assertTrue(request.contains("\"quality\":\"low\"")); + assertTrue(response.output().get(0).isImageGenerationCall()); + assertArrayEquals(png, + Base64.getDecoder().decode(response.output().get(0).asImageGenerationCall().result().get())); + httpClient.assertResponsesConsumed(); + } + + @Test + public void asyncImageGenerationResultDecodesKnownBytes() { + byte[] png = new byte[] { (byte) 0x89, 0x50, 0x4E, 0x47 }; + String base64 = Base64.getEncoder().encodeToString(png); + String imageOutput = "{\"id\":\"image-async\",\"type\":\"image_generation_call\"," + + "\"status\":\"completed\",\"result\":\"" + base64 + "\"}"; + DeterministicHttpClient httpClient = new DeterministicHttpClient().enqueueJson(200, + OpenAIResponseFixtures.response("resp-image-async", imageOutput)); + ResponsesAsyncClient responsesClient = createBuilder(httpClient).buildResponsesAsyncClient(); + + StepVerifier.create(responsesClient.createAzureResponse(agentOptions(), + ResponseCreateParams.builder().input("Generate an image."))).assertNext(response -> { + assertTrue(response.output().get(0).isImageGenerationCall()); + assertArrayEquals(png, + Base64.getDecoder().decode(response.output().get(0).asImageGenerationCall().result().get())); + }).verifyComplete(); + httpClient.assertResponsesConsumed(); + } + + @Test + public void mcpProjectConnectionApprovalContinuesResponse() { + String approvalRequest = "{\"id\":\"approval-1\",\"type\":\"mcp_approval_request\"," + + "\"arguments\":\"{}\",\"name\":\"get_profile\",\"server_label\":\"github\"}"; + DeterministicHttpClient httpClient + = new DeterministicHttpClient().enqueueJson(200, OpenAIResponseFixtures.promptAgentVersion(AGENT_NAME, "1")) + .enqueueJson(200, OpenAIResponseFixtures.response("resp-approval", approvalRequest)) + .enqueueJson(200, OpenAIResponseFixtures.response("resp-approved", + OpenAIResponseFixtures.message("msg-approved", "Profile retrieved."))); + AgentsClientBuilder builder = createBuilder(httpClient); + McpTool mcpTool = new McpTool("github").setServerUrl("https://example.test/mcp") + .setProjectConnectionId("mcp-connection") + .setRequireApproval("always"); + + builder.buildAgentsClient().createAgentVersion(AGENT_NAME, definition(mcpTool)); + ResponsesClient responsesClient = builder.buildResponsesClient(); + Response initial = responsesClient.createAzureResponse(agentOptions(), + ResponseCreateParams.builder().conversation("conversation-mcp").input("Get my profile.")); + ResponseOutputItem.McpApprovalRequest request = initial.output().get(0).asMcpApprovalRequest(); + ResponseInputItem approval = ResponseInputItem.ofMcpApprovalResponse( + ResponseInputItem.McpApprovalResponse.builder().approvalRequestId(request.id()).approve(true).build()); + Response completed = responsesClient.createAzureResponse(agentOptions(), + ResponseCreateParams.builder() + .conversation("conversation-mcp") + .inputOfResponse(Collections.singletonList(approval)) + .previousResponseId(initial.id())); + + assertEquals("github", request.serverLabel()); + assertEquals("get_profile", request.name()); + assertEquals("Profile retrieved.", responseText(completed)); + assertTrue(body(httpClient.getRequest(0)).contains("\"project_connection_id\":\"mcp-connection\"")); + String continuation = body(httpClient.getRequest(2)); + assertTrue(continuation.contains("\"type\":\"mcp_approval_response\"")); + assertTrue(continuation.contains("\"approval_request_id\":\"approval-1\"")); + assertTrue(continuation.contains("\"approve\":true")); + assertTrue(continuation.contains("\"previous_response_id\":\"resp-approval\"")); + assertTrue(continuation.contains("\"conversation\":\"conversation-mcp\"")); + httpClient.assertResponsesConsumed(); + } + + @Test + public void fileSearchMultiTurnPreservesConversationAndPreviousResponse() { + String fileSearch = "{\"id\":\"file-search-1\",\"type\":\"file_search_call\"," + + "\"queries\":[\"product documentation\"],\"status\":\"completed\",\"results\":[]}"; + DeterministicHttpClient httpClient = new DeterministicHttpClient() + .enqueueJson(200, + OpenAIResponseFixtures.response("resp-file-search", + fileSearch + "," + OpenAIResponseFixtures.message("msg-search-1", "First result."))) + .enqueueJson(200, OpenAIResponseFixtures.response("resp-file-search-follow-up", + OpenAIResponseFixtures.message("msg-search-2", "Follow-up result."))); + ResponsesClient responsesClient = createBuilder(httpClient).buildResponsesClient(); + + Response first = responsesClient.createAzureResponse(agentOptions(), + ResponseCreateParams.builder() + .conversation("conversation-file-search") + .input("Search the product documentation.")); + assertTrue(first.output().get(0).isFileSearchCall()); + assertEquals("product documentation", first.output().get(0).asFileSearchCall().queries().get(0)); + Response second = responsesClient.createAzureResponse(agentOptions(), + ResponseCreateParams.builder() + .conversation("conversation-file-search") + .input("Tell me more.") + .previousResponseId(first.id())); + + assertEquals("Follow-up result.", responseText(second)); + String continuation = body(httpClient.getRequest(1)); + assertTrue(continuation.contains("\"conversation\":\"conversation-file-search\"")); + assertTrue(continuation.contains("\"previous_response_id\":\"resp-file-search\"")); + assertTrue(continuation.contains("Tell me more.")); + httpClient.assertResponsesConsumed(); + } + + @Test + public void connectionBackedToolsSerializeTheirConfiguration() { + DeterministicHttpClient httpClient = new DeterministicHttpClient(); + for (int i = 1; i <= 5; i++) { + httpClient.enqueueJson(200, OpenAIResponseFixtures.promptAgentVersion(AGENT_NAME, Integer.toString(i))); + } + AgentsClient client = createBuilder(httpClient).allowPreview(true).buildAgentsClient(); + + client.createAgentVersion(AGENT_NAME, + definition(new McpTool("github").setServerUrl("https://example.test/mcp") + .setProjectConnectionId("mcp-connection") + .setRequireApproval("always"))); + client.createAgentVersion(AGENT_NAME, definition(openApiTool())); + client.createAgentVersion(AGENT_NAME, + definition(new AzureAISearchTool(new AzureAISearchToolResource( + Collections.singletonList(new AISearchIndexResource().setProjectConnectionId("search-connection") + .setIndexName("products") + .setQueryType(AzureAISearchQueryType.SEMANTIC) + .setTopK(5)))))); + client.createAgentVersion(AGENT_NAME, + definition(new BingGroundingTool(new BingGroundingSearchToolParameters(Collections.singletonList( + new BingGroundingSearchConfiguration("bing-connection").setMarket("en-US").setCount(3L)))))); + client.createAgentVersion(AGENT_NAME, + definition(new MemorySearchPreviewTool("memory-store", "user-123").setUpdateDelaySeconds(1))); + + String mcp = body(httpClient.getRequest(0)); + assertTrue(mcp.contains("\"project_connection_id\":\"mcp-connection\"")); + assertTrue(mcp.contains("\"require_approval\":\"always\"")); + String openApi = body(httpClient.getRequest(1)); + assertTrue(openApi.contains("\"type\":\"project_connection\"")); + assertTrue(openApi.contains("\"project_connection_id\":\"openapi-connection\"")); + String search = body(httpClient.getRequest(2)); + assertTrue(search.contains("\"type\":\"azure_ai_search\"")); + assertTrue(search.contains("\"index_name\":\"products\"")); + assertTrue(search.contains("\"query_type\":\"semantic\"")); + String bing = body(httpClient.getRequest(3)); + assertTrue(bing.contains("\"type\":\"bing_grounding\"")); + assertTrue(bing.contains("\"project_connection_id\":\"bing-connection\"")); + String memory = body(httpClient.getRequest(4)); + assertTrue(memory.contains("\"type\":\"memory_search_preview\"")); + assertTrue(memory.contains("\"memory_store_name\":\"memory-store\"")); + assertTrue(memory.contains("\"scope\":\"user-123\"")); + assertTrue(memory.contains("\"update_delay\":1")); + httpClient.assertResponsesConsumed(); + } + + private static AgentsClientBuilder createBuilder(DeterministicHttpClient httpClient) { + return new AgentsClientBuilder().endpoint(ENDPOINT) + .credential(new MockTokenCredential()) + .httpClient(httpClient) + .serviceVersion(AgentsServiceVersion.V1); + } + + private static AzureCreateResponseOptions agentOptions() { + return new AzureCreateResponseOptions().setAgentReference(new AgentReference(AGENT_NAME).setVersion("1")); + } + + private static CreateAgentVersionInput definition(Tool tool) { + return new CreateAgentVersionInput( + new PromptAgentDefinition("gpt-4o").setTools(Collections.singletonList(tool))); + } + + private static OpenApiTool openApiTool() { + Map spec = new LinkedHashMap<>(); + spec.put("openapi", BinaryData.fromObject("3.0.0")); + spec.put("info", BinaryData.fromObject(Collections.singletonMap("title", "Test API"))); + spec.put("paths", BinaryData.fromObject(Collections.emptyMap())); + return new OpenApiTool(new OpenApiFunctionDefinition("test_api", spec, + new OpenApiProjectConnectionAuthDetails(new OpenApiProjectConnectionSecurityScheme("openapi-connection")))); + } + + private static String responseText(Response response) { + return response.output().get(0).asMessage().content().get(0).asOutputText().text(); + } + + private static String body(HttpRequest request) { + return request.getBodyAsBinaryData().toString(); + } + + private static byte[] readBytes(InputStream inputStream) throws IOException { + ByteArrayOutputStream output = new ByteArrayOutputStream(); + byte[] buffer = new byte[1024]; + int read; + while ((read = inputStream.read(buffer)) != -1) { + output.write(buffer, 0, read); + } + return output.toByteArray(); + } +} diff --git a/sdk/ai/azure-ai-agents/src/test/java/com/azure/ai/agents/ToolStreamingMockTests.java b/sdk/ai/azure-ai-agents/src/test/java/com/azure/ai/agents/ToolStreamingMockTests.java new file mode 100644 index 0000000000000..72e526adef017 --- /dev/null +++ b/sdk/ai/azure-ai-agents/src/test/java/com/azure/ai/agents/ToolStreamingMockTests.java @@ -0,0 +1,147 @@ +// 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.AzureCreateResponseOptions; +import com.azure.core.http.HttpRequest; +import com.azure.core.test.utils.MockTokenCredential; +import com.azure.core.util.IterableStream; +import com.openai.models.responses.ResponseCreateParams; +import com.openai.models.responses.ResponseStreamEvent; +import org.junit.jupiter.api.Test; +import reactor.test.StepVerifier; + +import java.util.ArrayList; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class ToolStreamingMockTests { + private static final String ENDPOINT = "https://localhost:8080/api/projects/project"; + private static final String AGENT_NAME = "streaming-tool-agent"; + + @Test + public void syncFileSearchStreamDeserializesLifecycleEvents() { + DeterministicHttpClient httpClient + = new DeterministicHttpClient().enqueueSse(fileSearchSse("resp-stream-sync")); + ResponsesClient client = createBuilder(httpClient).buildResponsesClient(); + + IterableStream stream = client.createStreamingAzureResponse(agentOptions(), + ResponseCreateParams.builder().input("Search the product documentation.")); + List events = new ArrayList<>(); + stream.forEach(events::add); + + assertFileSearchEvents(events, "resp-stream-sync"); + assertStreamingRequest(httpClient.getRequest(0)); + httpClient.assertResponsesConsumed(); + } + + @Test + public void bingGroundingStreamDeserializesUrlCitation() { + DeterministicHttpClient httpClient = new DeterministicHttpClient().enqueueSse(bingCitationSse()); + ResponsesClient client = createBuilder(httpClient).buildResponsesClient(); + + List events = new ArrayList<>(); + client + .createStreamingAzureResponse(agentOptions(), + ResponseCreateParams.builder().input("What is the current weather?")) + .forEach(events::add); + + assertEquals(2, events.size()); + com.openai.models.responses.ResponseOutputText.Annotation.UrlCitation citation = events.get(0) + .outputItemDone() + .get() + .item() + .asMessage() + .content() + .get(0) + .asOutputText() + .annotations() + .get(0) + .asUrlCitation(); + assertEquals("https://example.test/weather", citation.url()); + assertEquals("Weather source", citation.title()); + assertEquals("resp-bing-stream", events.get(1).completed().get().response().id()); + httpClient.assertResponsesConsumed(); + } + + @Test + public void asyncFileSearchStreamDeserializesLifecycleEvents() { + DeterministicHttpClient httpClient + = new DeterministicHttpClient().enqueueSse(fileSearchSse("resp-stream-async")); + ResponsesAsyncClient client = createBuilder(httpClient).buildResponsesAsyncClient(); + + StepVerifier + .create( + client + .createStreamingAzureResponse(agentOptions(), + ResponseCreateParams.builder().input("Search the product documentation.")) + .collectList()) + .assertNext(events -> assertFileSearchEvents(events, "resp-stream-async")) + .verifyComplete(); + assertStreamingRequest(httpClient.getRequest(0)); + httpClient.assertResponsesConsumed(); + } + + private static AgentsClientBuilder createBuilder(DeterministicHttpClient httpClient) { + return new AgentsClientBuilder().endpoint(ENDPOINT) + .credential(new MockTokenCredential()) + .httpClient(httpClient) + .serviceVersion(AgentsServiceVersion.V1); + } + + private static AzureCreateResponseOptions agentOptions() { + return new AzureCreateResponseOptions().setAgentReference(new AgentReference(AGENT_NAME).setVersion("1")); + } + + private static void assertFileSearchEvents(List events, String responseId) { + assertEquals(5, events.size()); + assertEquals("file-search-item", events.get(0).fileSearchCallInProgress().get().itemId()); + assertEquals("file-search-item", events.get(1).fileSearchCallSearching().get().itemId()); + assertEquals("file-search-item", events.get(2).fileSearchCallCompleted().get().itemId()); + assertEquals("Product documentation result.", + events.get(3).outputItemDone().get().item().asMessage().content().get(0).asOutputText().text()); + assertEquals(responseId, events.get(4).completed().get().response().id()); + } + + private static void assertStreamingRequest(HttpRequest request) { + String requestBody = request.getBodyAsBinaryData().toString(); + assertTrue(request.getUrl().getPath().endsWith("/openai/v1/responses")); + assertTrue(requestBody.contains("\"stream\":true")); + assertTrue(requestBody.contains("\"agent_reference\"")); + assertTrue(requestBody.contains("Search the product documentation.")); + } + + private static String bingCitationSse() { + String citation = "{\"type\":\"url_citation\",\"url\":\"https://example.test/weather\"," + + "\"title\":\"Weather source\",\"start_index\":0,\"end_index\":14}"; + String message = OpenAIResponseFixtures.message("message-bing", "Weather report.", citation); + String response = OpenAIResponseFixtures.response("resp-bing-stream", message); + return event("{\"type\":\"response.output_item.done\",\"output_index\":0," + "\"sequence_number\":0,\"item\":" + + message + "}") + + event("{\"type\":\"response.completed\",\"sequence_number\":1,\"response\":" + response + "}") + + "data: [DONE]\n\n"; + } + + private static String fileSearchSse(String responseId) { + String message = OpenAIResponseFixtures.message("message-item", "Product documentation result."); + String response = OpenAIResponseFixtures.response(responseId, message); + return event("{\"type\":\"response.file_search_call.in_progress\"," + + "\"item_id\":\"file-search-item\",\"output_index\":0,\"sequence_number\":0}") + + event("{\"type\":\"response.file_search_call.searching\"," + + "\"item_id\":\"file-search-item\",\"output_index\":0,\"sequence_number\":1}") + + event("{\"type\":\"response.file_search_call.completed\"," + + "\"item_id\":\"file-search-item\",\"output_index\":0,\"sequence_number\":2}") + + event("{\"type\":\"response.output_item.done\",\"output_index\":1," + "\"sequence_number\":3,\"item\":" + + message + "}") + + event("{\"type\":\"response.completed\",\"sequence_number\":4,\"response\":" + response + "}") + + "data: [DONE]\n\n"; + } + + private static String event(String json) { + return "data: " + json + "\n\n"; + } +} diff --git a/sdk/ai/azure-ai-agents/src/test/java/com/azure/ai/agents/VoiceAgentsMockTests.java b/sdk/ai/azure-ai-agents/src/test/java/com/azure/ai/agents/VoiceAgentsMockTests.java new file mode 100644 index 0000000000000..c7e6e14040a5f --- /dev/null +++ b/sdk/ai/azure-ai-agents/src/test/java/com/azure/ai/agents/VoiceAgentsMockTests.java @@ -0,0 +1,225 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.ai.agents; + +import com.azure.ai.agents.models.AgentDetails; +import com.azure.ai.agents.models.AgentKind; +import com.azure.ai.agents.models.AgentState; +import com.azure.ai.agents.models.AgentVersionDetails; +import com.azure.ai.agents.models.CreateAgentVersionInput; +import com.azure.ai.agents.models.VoiceAgentAudioConfig; +import com.azure.ai.agents.models.VoiceAgentAudioOutputConfig; +import com.azure.ai.agents.models.VoiceAgentDefinition; +import com.azure.ai.agents.models.VoiceModelType; +import com.azure.ai.agents.models.VoiceOutputModality; +import com.azure.ai.agents.models.VoiceType; +import com.azure.core.http.HttpHeaderName; +import com.azure.core.http.HttpMethod; +import com.azure.core.http.HttpRequest; +import com.azure.core.test.utils.MockTokenCredential; +import org.junit.jupiter.api.Test; +import reactor.test.StepVerifier; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class VoiceAgentsMockTests { + private static final String ENDPOINT = "https://localhost:8080/api/projects/project"; + private static final String AGENT_NAME = "voice-agent-test"; + private static final HttpHeaderName FOUNDRY_FEATURES = HttpHeaderName.fromString("Foundry-Features"); + + @Test + public void syncVoiceAgentCrudUsesExpectedWireShapeAndPaths() { + DeterministicHttpClient httpClient + = new DeterministicHttpClient().enqueueJson(200, versionJson("1", "Initial instructions")) + .enqueueJson(200, agentJson("disabled", "1")) + .enqueueJson(204, "") + .enqueueJson(200, agentJson("enabled", "1")) + .enqueueJson(200, ""); + AgentsClient client = createBuilder(httpClient).allowPreview(true).buildAgentsClient(); + + AgentVersionDetails created = client.createAgentVersion(AGENT_NAME, + new CreateAgentVersionInput(createVoiceDefinition("Initial instructions"))); + assertEquals(AGENT_NAME, created.getName()); + assertEquals("1", created.getVersion()); + assertInstanceOf(VoiceAgentDefinition.class, created.getDefinition()); + + HttpRequest createRequest = httpClient.getRequest(0); + assertEquals(HttpMethod.POST, createRequest.getHttpMethod()); + assertTrue(createRequest.getUrl().getPath().endsWith("/agents/" + AGENT_NAME + "/versions")); + String requestBody = createRequest.getBodyAsBinaryData().toString(); + assertTrue(requestBody.contains("\"kind\":\"voice\"")); + assertTrue(requestBody.contains("\"model_type\":\"managed\"")); + assertTrue(requestBody.contains("\"voice\":\"en-US-AvaNeural\"")); + assertTrue(requestBody.contains("\"output_modalities\":[\"audio\"]")); + assertTrue(createRequest.getHeaders().getValue(FOUNDRY_FEATURES).contains("VoiceAgents=V1Preview")); + + AgentDetails disabled = client.getAgent(AGENT_NAME); + assertEquals(AgentState.DISABLED, disabled.getState()); + client.enableAgent(AGENT_NAME); + AgentDetails enabled = client.getAgent(AGENT_NAME); + assertEquals(AgentState.ENABLED, enabled.getState()); + client.deleteAgent(AGENT_NAME); + + assertTrue(httpClient.getRequest(1).getUrl().getPath().endsWith("/agents/" + AGENT_NAME)); + assertTrue(httpClient.getRequest(2).getUrl().getPath().endsWith("/agents/" + AGENT_NAME + ":enable")); + assertTrue(httpClient.getRequest(3).getUrl().getPath().endsWith("/agents/" + AGENT_NAME)); + assertEquals(HttpMethod.DELETE, httpClient.getRequest(4).getHttpMethod()); + } + + @Test + public void asyncVoiceAgentCrudUsesExpectedWireShapeAndPaths() { + DeterministicHttpClient httpClient + = new DeterministicHttpClient().enqueueJson(200, versionJson("2", "Async instructions")) + .enqueueJson(200, agentJson("enabled", "2")) + .enqueueJson(204, "") + .enqueueJson(200, agentJson("disabled", "2")) + .enqueueJson(200, ""); + AgentsAsyncClient client = createBuilder(httpClient).allowPreview(true).buildAgentsAsyncClient(); + + StepVerifier.create(client + .createAgentVersion(AGENT_NAME, new CreateAgentVersionInput(createVoiceDefinition("Async instructions"))) + .doOnNext(created -> { + assertEquals("2", created.getVersion()); + assertInstanceOf(VoiceAgentDefinition.class, created.getDefinition()); + }) + .then(client.getAgent(AGENT_NAME)) + .doOnNext(agent -> assertEquals(AgentState.ENABLED, agent.getState())) + .then(client.disableAgent(AGENT_NAME)) + .then(client.getAgent(AGENT_NAME)) + .doOnNext(agent -> assertEquals(AgentState.DISABLED, agent.getState())) + .then(client.deleteAgent(AGENT_NAME))).verifyComplete(); + + assertEquals(5, httpClient.getRequests().size()); + HttpRequest createRequest = httpClient.getRequest(0); + assertTrue(createRequest.getBodyAsBinaryData().toString().contains("\"kind\":\"voice\"")); + assertTrue(createRequest.getHeaders().getValue(FOUNDRY_FEATURES).contains("VoiceAgents=V1Preview")); + assertTrue(httpClient.getRequest(2).getUrl().getPath().endsWith("/agents/" + AGENT_NAME + ":disable")); + } + + @Test + public void syncVoiceAgentVersionLifecycleMatchesPythonCrud() { + DeterministicHttpClient httpClient + = new DeterministicHttpClient().enqueueJson(200, versionJson("1", "Initial instructions")) + .enqueueJson(200, versionJson("2", "Updated instructions")) + .enqueueJson(200, agentJson("enabled", "2")) + .enqueueJson(200, versionJson("1", "Initial instructions")) + .enqueueJson(200, versionsJson()) + .enqueueJson(200, ""); + AgentsClient client = createBuilder(httpClient).allowPreview(true).buildAgentsClient(); + + AgentVersionDetails first = client.createAgentVersion(AGENT_NAME, + new CreateAgentVersionInput(createVoiceDefinition("Initial instructions"))); + AgentVersionDetails second = client.createAgentVersion(AGENT_NAME, + new CreateAgentVersionInput(createVoiceDefinition("Updated instructions"))); + AgentDetails agent = client.getAgent(AGENT_NAME); + AgentVersionDetails retrieved = client.getAgentVersionDetails(AGENT_NAME, first.getVersion()); + List versions = new ArrayList<>(); + client.listAgentVersions(AGENT_NAME).forEach(versions::add); + client.deleteAgent(AGENT_NAME); + + assertEquals("1", first.getVersion()); + assertEquals("2", second.getVersion()); + assertEquals("2", agent.getVersions().getLatest().getVersion()); + assertEquals("1", retrieved.getVersion()); + assertEquals(2, versions.size()); + assertEquals("1", versions.get(0).getVersion()); + assertEquals("2", versions.get(1).getVersion()); + assertTrue(httpClient.getRequest(3).getUrl().getPath().endsWith("/versions/1")); + assertTrue(httpClient.getRequest(4).getUrl().getPath().endsWith("/versions")); + assertEquals(HttpMethod.DELETE, httpClient.getRequest(5).getHttpMethod()); + httpClient.assertResponsesConsumed(); + } + + @Test + public void asyncVoiceAgentVersionLifecycleMatchesPythonCrud() { + DeterministicHttpClient httpClient + = new DeterministicHttpClient().enqueueJson(200, versionJson("1", "Initial instructions")) + .enqueueJson(200, versionJson("2", "Updated instructions")) + .enqueueJson(200, agentJson("enabled", "2")) + .enqueueJson(200, versionJson("1", "Initial instructions")) + .enqueueJson(200, versionsJson()) + .enqueueJson(200, ""); + AgentsAsyncClient client = createBuilder(httpClient).allowPreview(true).buildAgentsAsyncClient(); + + StepVerifier.create(client + .createAgentVersion(AGENT_NAME, new CreateAgentVersionInput(createVoiceDefinition("Initial instructions"))) + .doOnNext(version -> assertEquals("1", version.getVersion())) + .then(client.createAgentVersion(AGENT_NAME, + new CreateAgentVersionInput(createVoiceDefinition("Updated instructions")))) + .doOnNext(version -> assertEquals("2", version.getVersion())) + .then(client.getAgent(AGENT_NAME)) + .doOnNext(agent -> assertEquals("2", agent.getVersions().getLatest().getVersion())) + .then(client.getAgentVersionDetails(AGENT_NAME, "1")) + .doOnNext(version -> assertEquals("1", version.getVersion())) + .thenMany(client.listAgentVersions(AGENT_NAME)) + .collectList() + .doOnNext(versions -> { + assertEquals(2, versions.size()); + assertEquals("1", versions.get(0).getVersion()); + assertEquals("2", versions.get(1).getVersion()); + }) + .then(client.deleteAgent(AGENT_NAME))).verifyComplete(); + + assertTrue(httpClient.getRequest(3).getUrl().getPath().endsWith("/versions/1")); + assertTrue(httpClient.getRequest(4).getUrl().getPath().endsWith("/versions")); + assertEquals(HttpMethod.DELETE, httpClient.getRequest(5).getHttpMethod()); + httpClient.assertResponsesConsumed(); + } + + @Test + public void getVoiceVersionDeserializesRequestedVersion() { + DeterministicHttpClient httpClient + = new DeterministicHttpClient().enqueueJson(200, versionJson("7", "Version seven")); + AgentsClient client = createBuilder(httpClient).allowPreview(true).buildAgentsClient(); + + AgentVersionDetails version = client.getAgentVersionDetails(AGENT_NAME, "7"); + + assertEquals("7", version.getVersion()); + assertEquals(AgentKind.VOICE, version.getDefinition().getKind()); + assertNotNull(((VoiceAgentDefinition) version.getDefinition()).getAudio()); + assertTrue(httpClient.getRequest(0).getUrl().getPath().endsWith("/agents/" + AGENT_NAME + "/versions/7")); + } + + private static AgentsClientBuilder createBuilder(DeterministicHttpClient httpClient) { + return new AgentsClientBuilder().endpoint(ENDPOINT) + .credential(new MockTokenCredential()) + .httpClient(httpClient) + .serviceVersion(AgentsServiceVersion.V1); + } + + private static VoiceAgentDefinition createVoiceDefinition(String instructions) { + VoiceAgentAudioOutputConfig output + = new VoiceAgentAudioOutputConfig().setVoice("en-US-AvaNeural").setVoiceType(VoiceType.AZURE_STANDARD); + return new VoiceAgentDefinition(VoiceModelType.MANAGED, "gpt-realtime").setInstructions(instructions) + .setAudio(new VoiceAgentAudioConfig().setOutput(output)) + .setOutputModalities(Collections.singletonList(VoiceOutputModality.AUDIO)) + .setStore(true); + } + + private static String versionJson(String version, String instructions) { + return "{\"object\":\"agent.version\",\"id\":\"agent-" + version + "\"," + "\"name\":\"" + AGENT_NAME + + "\",\"version\":\"" + version + "\"," + + "\"created_at\":1,\"metadata\":{},\"definition\":{\"kind\":\"voice\"," + + "\"model_type\":\"managed\",\"model\":\"gpt-realtime\"," + "\"instructions\":\"" + instructions + + "\",\"audio\":{\"output\":{" + "\"voice\":\"en-US-AvaNeural\",\"voice_type\":\"azure-standard\"}}," + + "\"output_modalities\":[\"audio\"],\"store\":true}}"; + } + + private static String versionsJson() { + return "{\"object\":\"list\",\"data\":[" + versionJson("1", "Initial instructions") + "," + + versionJson("2", "Updated instructions") + "],\"has_more\":false}"; + } + + private static String agentJson(String state, String latestVersion) { + return "{\"object\":\"agent\",\"id\":\"agent-id\",\"name\":\"" + AGENT_NAME + "\",\"state\":\"" + state + + "\",\"versions\":{\"latest\":" + versionJson(latestVersion, "Latest instructions") + "}}"; + } +} diff --git a/sdk/ai/azure-ai-agents/src/test/java/com/azure/ai/agents/VoiceConversationMockTests.java b/sdk/ai/azure-ai-agents/src/test/java/com/azure/ai/agents/VoiceConversationMockTests.java new file mode 100644 index 0000000000000..94c32c622df90 --- /dev/null +++ b/sdk/ai/azure-ai-agents/src/test/java/com/azure/ai/agents/VoiceConversationMockTests.java @@ -0,0 +1,122 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.ai.agents; + +import com.azure.ai.agents.models.VoiceAudioContainerFormat; +import com.azure.ai.agents.models.VoiceAudioRole; +import com.azure.ai.agents.models.VoiceConversation; +import com.azure.ai.agents.models.VoiceConversationStatus; +import com.azure.ai.agents.models.VoiceRecordingResponse; +import com.azure.ai.agents.models.VoiceResponse; +import com.azure.ai.agents.models.VoiceResponseBaseStatus; +import com.azure.core.http.HttpHeaders; +import com.azure.core.test.utils.MockTokenCredential; +import com.azure.core.util.BinaryData; +import org.junit.jupiter.api.Test; +import reactor.core.publisher.Mono; +import reactor.test.StepVerifier; + +import java.nio.charset.StandardCharsets; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class VoiceConversationMockTests { + private static final String ENDPOINT = "https://localhost:8080/api/projects/project"; + private static final String AGENT_NAME = "voice-agent-test"; + private static final String CONVERSATION_ID = "conversation-test"; + private static final byte[] WAV_BYTES = "RIFF-test-wave".getBytes(StandardCharsets.UTF_8); + + @Test + public void syncConversationAndAudioOperationsDeserializeResponses() { + DeterministicHttpClient httpClient = new DeterministicHttpClient().enqueueJson(200, conversationJson()) + .enqueueJson(200, responseJson()) + .enqueueJson(200, recordingJson()) + .enqueue(200, new HttpHeaders().set(com.azure.core.http.HttpHeaderName.CONTENT_TYPE, "audio/wav"), + WAV_BYTES); + BetaAgentEndpointConversationsClient client + = createBuilder(httpClient).buildBetaAgentEndpointConversationsClient(); + + VoiceConversation conversation = client.getAgentConversation(AGENT_NAME, CONVERSATION_ID); + assertEquals(CONVERSATION_ID, conversation.getId()); + assertEquals(VoiceConversationStatus.COMPLETED, conversation.getStatus()); + + VoiceResponse response = client.getAgentConversationResponse(AGENT_NAME, CONVERSATION_ID, "response-test"); + assertEquals("response-test", response.getId()); + assertEquals(VoiceResponseBaseStatus.COMPLETED, response.getStatus()); + + VoiceRecordingResponse recording = client.getAgentConversationAudio(AGENT_NAME, CONVERSATION_ID); + assertEquals(VoiceAudioContainerFormat.WAV, recording.getFormat()); + assertEquals(24000, recording.getSampleRate()); + assertEquals(2, recording.getChannels()); + assertEquals("user", recording.getChannelLayout().getLeft()); + assertEquals("agent", recording.getChannelLayout().getRight()); + + BinaryData content = client.getAgentConversationAudioContent(AGENT_NAME, CONVERSATION_ID); + assertArrayEquals(WAV_BYTES, content.toBytes()); + assertTrue(httpClient.getRequest(0) + .getUrl() + .getPath() + .endsWith("/agents/" + AGENT_NAME + "/endpoint/protocols/voice/conversations/" + CONVERSATION_ID)); + assertTrue(httpClient.getRequest(3).getUrl().getPath().endsWith("/audio/content")); + } + + @Test + public void asyncConversationItemAudioOperationsDeserializeResponses() { + DeterministicHttpClient httpClient = new DeterministicHttpClient().enqueueJson(200, conversationJson()) + .enqueueJson(200, itemAudioJson()) + .enqueue(200, new HttpHeaders().set(com.azure.core.http.HttpHeaderName.CONTENT_TYPE, "audio/wav"), + WAV_BYTES); + BetaAgentEndpointConversationsAsyncClient client + = createBuilder(httpClient).buildBetaAgentEndpointConversationsAsyncClient(); + + Mono operations = client.getAgentConversation(AGENT_NAME, CONVERSATION_ID).doOnNext(conversation -> { + assertEquals(CONVERSATION_ID, conversation.getId()); + assertEquals(VoiceConversationStatus.COMPLETED, conversation.getStatus()); + }).then(client.getAgentConversationItemAudio(AGENT_NAME, CONVERSATION_ID, "item-test")).doOnNext(itemAudio -> { + assertEquals("item-test", itemAudio.getItemId()); + assertEquals(VoiceAudioRole.USER, itemAudio.getRole()); + assertEquals(VoiceAudioContainerFormat.WAV, itemAudio.getFormat()); + }) + .then(client.getAgentConversationItemAudioContent(AGENT_NAME, CONVERSATION_ID, "item-test")) + .doOnNext(content -> assertArrayEquals(WAV_BYTES, content.toBytes())) + .then(); + + StepVerifier.create(operations).verifyComplete(); + assertEquals(3, httpClient.getRequests().size()); + assertTrue(httpClient.getRequest(1).getUrl().getPath().endsWith("/items/item-test/audio")); + assertTrue(httpClient.getRequest(2).getUrl().getPath().endsWith("/items/item-test/audio/content")); + } + + private static AgentsClientBuilder createBuilder(DeterministicHttpClient httpClient) { + return new AgentsClientBuilder().endpoint(ENDPOINT) + .credential(new MockTokenCredential()) + .httpClient(httpClient) + .serviceVersion(AgentsServiceVersion.V1); + } + + private static String conversationJson() { + return "{\"id\":\"" + CONVERSATION_ID + "\",\"object\":\"voice.conversation\"," + + "\"status\":\"completed\",\"created_at\":1,\"completed_at\":2,\"metadata\":{}}"; + } + + private static String responseJson() { + return "{\"id\":\"response-test\",\"conversation_id\":\"" + CONVERSATION_ID + "\"," + + "\"object\":\"realtime.response\",\"status\":\"completed\"," + + "\"output_modalities\":[\"audio\"],\"output\":[]}"; + } + + private static String recordingJson() { + return "{\"conversation_id\":\"" + CONVERSATION_ID + "\",\"format\":\"wav\"," + + "\"sample_rate\":24000,\"channels\":2," + + "\"channel_layout\":{\"left\":\"user\",\"right\":\"agent\"},\"duration_ms\":1500}"; + } + + private static String itemAudioJson() { + return "{\"conversation_id\":\"" + CONVERSATION_ID + "\",\"item_id\":\"item-test\"," + + "\"role\":\"user\",\"format\":\"wav\",\"codec\":\"pcm16\"," + + "\"sample_rate\":24000,\"channels\":1,\"duration_ms\":500}"; + } +} diff --git a/sdk/ai/azure-ai-agents/src/test/java/com/azure/ai/agents/models/AdvancedAgentDefinitionSerializationTests.java b/sdk/ai/azure-ai-agents/src/test/java/com/azure/ai/agents/models/AdvancedAgentDefinitionSerializationTests.java new file mode 100644 index 0000000000000..7521fa7b7b887 --- /dev/null +++ b/sdk/ai/azure-ai-agents/src/test/java/com/azure/ai/agents/models/AdvancedAgentDefinitionSerializationTests.java @@ -0,0 +1,168 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.ai.agents.models; + +import com.azure.core.util.BinaryData; +import com.azure.json.JsonProviders; +import com.azure.json.JsonReader; +import com.azure.json.JsonWriter; +import org.junit.jupiter.api.Test; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class AdvancedAgentDefinitionSerializationTests { + + @Test + public void structuredOutputCalendarSchemaRoundTrips() throws IOException { + Map properties = new LinkedHashMap<>(); + properties.put("name", Collections.singletonMap("type", "string")); + properties.put("date", field("string", "Date in YYYY-MM-DD format")); + properties.put("participants", arrayField("string")); + Map schema = new LinkedHashMap<>(); + schema.put("type", "object"); + schema.put("properties", properties); + schema.put("required", Arrays.asList("name", "date", "participants")); + schema.put("additionalProperties", false); + + ResponseFormatJsonSchemaInner schemaModel + = BinaryData.fromObject(schema).toObject(ResponseFormatJsonSchemaInner.class); + PromptAgentDefinition original + = new PromptAgentDefinition("gpt-4o").setInstructions("Extract a calendar event.") + .setText(new PromptAgentDefinitionTextOptions() + .setFormat(new TextResponseFormatJsonSchema("CalendarEvent", schemaModel).setStrict(true))); + + String json = serialize(original); + assertTrue(json.contains("\"name\":\"CalendarEvent\"")); + assertTrue(json.contains("\"type\":\"json_schema\"")); + assertTrue(json.contains("\"strict\":true")); + assertTrue(json.contains("\"additionalProperties\":false")); + assertTrue(json.contains("\"participants\"")); + + PromptAgentDefinition deserialized; + try (JsonReader reader = JsonProviders.createReader(json)) { + deserialized = PromptAgentDefinition.fromJson(reader); + } + assertNotNull(deserialized.getText()); + assertInstanceOf(TextResponseFormatJsonSchema.class, deserialized.getText().getFormat()); + TextResponseFormatJsonSchema format = (TextResponseFormatJsonSchema) deserialized.getText().getFormat(); + assertEquals("CalendarEvent", format.getName()); + assertEquals(Boolean.TRUE, format.isStrict()); + } + + @Test + public void workflowDefinitionRoundTrips() throws IOException { + String workflow = "kind: workflow\ntrigger:\n kind: OnConversationStart\n actions: []\n"; + WorkflowAgentDefinition original = new WorkflowAgentDefinition().setWorkflow(workflow); + + String json = serialize(original); + assertTrue(json.contains("\"kind\":\"workflow\"")); + assertTrue(json.contains("OnConversationStart")); + + AgentDefinition deserialized; + try (JsonReader reader = JsonProviders.createReader(json)) { + deserialized = AgentDefinition.fromJson(reader); + } + assertInstanceOf(WorkflowAgentDefinition.class, deserialized); + assertEquals(workflow, ((WorkflowAgentDefinition) deserialized).getWorkflow()); + } + + @Test + public void endpointRoutingSerializesResponsesProtocol() throws IOException { + AgentEndpointConfig endpoint = new AgentEndpointConfig() + .setVersionSelector(new VersionSelector().setVersionSelectionRules( + Collections.singletonList(new FixedRatioVersionSelectionRule(100).setAgentVersion("2")))) + .setProtocolConfiguration(new ProtocolConfiguration().setResponses(new ResponsesProtocolConfiguration())); + UpdateAgentDetailsOptions update = new UpdateAgentDetailsOptions().setAgentEndpoint(endpoint); + + String json = serialize(update); + assertTrue(json.contains("\"agent_endpoint\"")); + assertTrue(json.contains("\"agent_version\":\"2\"")); + assertTrue(json.contains("\"traffic_percentage\":100")); + assertTrue(json.contains("\"type\":\"FixedRatio\"")); + assertTrue(json.contains("\"responses\":{}")); + } + + @Test + public void multitoolDefinitionPreservesToolOrderAndTypes() throws IOException { + Map functionParameters = new LinkedHashMap<>(); + functionParameters.put("type", BinaryData.fromObject("object")); + functionParameters.put("properties", + BinaryData.fromObject(Collections.singletonMap("report_name", Collections.singletonMap("type", "string")))); + functionParameters.put("required", BinaryData.fromObject(Collections.singletonList("report_name"))); + functionParameters.put("additionalProperties", BinaryData.fromObject(false)); + + PromptAgentDefinition original = new PromptAgentDefinition("gpt-4o") + .setTools(Arrays.asList(new FileSearchTool(Collections.singletonList("vs_test")), + new CodeInterpreterTool().setContainer(new AutoCodeInterpreterToolParameter()), + new FunctionTool("save_analysis", functionParameters, true))); + + String json = serialize(original); + int fileSearch = json.indexOf("\"type\":\"file_search\""); + int codeInterpreter = json.indexOf("\"type\":\"code_interpreter\""); + int function = json.indexOf("\"type\":\"function\""); + assertTrue(fileSearch >= 0 && codeInterpreter > fileSearch && function > codeInterpreter); + assertTrue(json.contains("\"vector_store_ids\":[\"vs_test\"]")); + assertTrue(json.contains("\"name\":\"save_analysis\"")); + assertTrue(json.contains("\"strict\":true")); + assertFalse(json.contains("additionalProperties\":true")); + + PromptAgentDefinition deserialized; + try (JsonReader reader = JsonProviders.createReader(json)) { + deserialized = PromptAgentDefinition.fromJson(reader); + } + assertEquals(3, deserialized.getTools().size()); + assertInstanceOf(FileSearchTool.class, deserialized.getTools().get(0)); + assertInstanceOf(CodeInterpreterTool.class, deserialized.getTools().get(1)); + assertInstanceOf(FunctionTool.class, deserialized.getTools().get(2)); + } + + private static Map field(String type, String description) { + Map field = new LinkedHashMap<>(); + field.put("type", type); + field.put("description", description); + return field; + } + + private static Map arrayField(String itemType) { + Map field = new LinkedHashMap<>(); + field.put("type", "array"); + field.put("items", Collections.singletonMap("type", itemType)); + return field; + } + + private static String serialize(JsonSerializableModel model) throws IOException { + ByteArrayOutputStream output = new ByteArrayOutputStream(); + try (JsonWriter writer = JsonProviders.createWriter(output)) { + model.write(writer); + } + return output.toString("UTF-8"); + } + + private interface JsonSerializableModel { + void write(JsonWriter writer) throws IOException; + } + + private static String serialize(PromptAgentDefinition model) throws IOException { + return serialize(model::toJson); + } + + private static String serialize(WorkflowAgentDefinition model) throws IOException { + return serialize(model::toJson); + } + + private static String serialize(UpdateAgentDetailsOptions model) throws IOException { + return serialize(model::toJson); + } +} diff --git a/sdk/ai/azure-ai-agents/src/test/java/com/azure/ai/agents/models/VoiceAgentDefinitionSerializationTests.java b/sdk/ai/azure-ai-agents/src/test/java/com/azure/ai/agents/models/VoiceAgentDefinitionSerializationTests.java new file mode 100644 index 0000000000000..6353346836451 --- /dev/null +++ b/sdk/ai/azure-ai-agents/src/test/java/com/azure/ai/agents/models/VoiceAgentDefinitionSerializationTests.java @@ -0,0 +1,102 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.ai.agents.models; + +import com.azure.json.JsonProviders; +import com.azure.json.JsonReader; +import com.azure.json.JsonWriter; +import org.junit.jupiter.api.Test; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.Arrays; +import java.util.Collections; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class VoiceAgentDefinitionSerializationTests { + + @Test + public void fullVoiceDefinitionRoundTrips() throws IOException { + RealtimeAudioFormatsAudioPcm pcm + = new RealtimeAudioFormatsAudioPcm().setRate(RealtimeAudioFormatsAudioPcmRate.TWO_FOUR_ZERO_ZERO_ZERO); + VoiceAgentAudioInputConfig input = new VoiceAgentAudioInputConfig().setFormat(pcm) + .setTurnDetection(new VoiceAgentServerVadTurnDetection().setThreshold(0.5) + .setPrefixPaddingMs(300L) + .setSilenceDurationMs(500L)) + .setTranscription(new VoiceAgentInputTranscription(VoiceAgentInputTranscriptionModel.WHISPER_1)); + VoiceAgentAudioOutputConfig output = new VoiceAgentAudioOutputConfig().setFormat(pcm) + .setVoice("en-US-AvaNeural") + .setVoiceType(VoiceType.AZURE_STANDARD); + VoiceAgentFunctionTool functionTool + = new VoiceAgentFunctionTool("get_weather").setDescription("Get weather for a city.") + .setParameters(new RealtimeFunctionToolParameters()); + VoiceAgentSystemTool systemTool = new VoiceAgentSystemTool(VoiceAgentSystemToolName.END_CONVERSATION); + + VoiceAgentDefinition original = new VoiceAgentDefinition(VoiceModelType.MANAGED, "gpt-realtime") + .setInstructions("Keep replies short and natural.") + .setAudio(new VoiceAgentAudioConfig().setInput(input).setOutput(output)) + .setOutputModalities(Collections.singletonList(VoiceOutputModality.AUDIO)) + .setTools(Arrays.asList(functionTool, systemTool)) + .setStore(true); + + String json = serialize(original); + assertTrue(json.contains("\"kind\":\"voice\"")); + assertTrue(json.contains("\"model_type\":\"managed\"")); + assertTrue(json.contains("\"model\":\"gpt-realtime\"")); + assertTrue(json.contains("\"voice\":\"en-US-AvaNeural\"")); + assertTrue(json.contains("\"voice_type\":\"azure-standard\"")); + assertTrue(json.contains("\"rate\":24000")); + assertTrue(json.contains("\"type\":\"server_vad\"")); + assertTrue(json.contains("\"model\":\"whisper-1\"")); + assertTrue(json.contains("\"output_modalities\":[\"audio\"]")); + assertTrue(json.contains("\"store\":true")); + assertTrue(json.contains("\"name\":\"get_weather\"")); + assertTrue(json.contains("\"name\":\"end_conversation\"")); + + AgentDefinition deserialized; + try (JsonReader reader = JsonProviders.createReader(json)) { + deserialized = AgentDefinition.fromJson(reader); + } + assertInstanceOf(VoiceAgentDefinition.class, deserialized); + VoiceAgentDefinition voice = (VoiceAgentDefinition) deserialized; + assertEquals(AgentKind.VOICE, voice.getKind()); + assertEquals(VoiceModelType.MANAGED, voice.getModelType()); + assertEquals("gpt-realtime", voice.getModel()); + assertEquals(Boolean.TRUE, voice.isStore()); + assertEquals(VoiceOutputModality.AUDIO, voice.getOutputModalities().get(0)); + assertEquals(2, voice.getTools().size()); + assertInstanceOf(VoiceAgentFunctionTool.class, voice.getTools().get(0)); + assertInstanceOf(VoiceAgentSystemTool.class, voice.getTools().get(1)); + assertNotNull(voice.getAudio().getInput().getTurnDetection()); + } + + @Test + public void selfDeployedVoiceDefinitionRoundTrips() throws IOException { + VoiceAgentDefinition original + = new VoiceAgentDefinition(VoiceModelType.SELF_DEPLOYED, "customer-realtime-deployment") + .setInstructions("Use the customer deployment."); + + String json = serialize(original); + VoiceAgentDefinition deserialized; + try (JsonReader reader = JsonProviders.createReader(json)) { + deserialized = VoiceAgentDefinition.fromJson(reader); + } + + assertEquals(VoiceModelType.SELF_DEPLOYED, deserialized.getModelType()); + assertEquals("customer-realtime-deployment", deserialized.getModel()); + assertEquals("Use the customer deployment.", deserialized.getInstructions()); + } + + private static String serialize(VoiceAgentDefinition definition) throws IOException { + ByteArrayOutputStream output = new ByteArrayOutputStream(); + try (JsonWriter writer = JsonProviders.createWriter(output)) { + definition.toJson(writer); + } + return output.toString("UTF-8"); + } +} From bbb5d47569b6cd40354df9d7325896242c42aa22 Mon Sep 17 00:00:00 2001 From: Mike Guo Date: Wed, 2 Sep 2026 19:22:00 +0800 Subject: [PATCH 2/4] Add synchronous Azure AI Agents samples Co-Authored-By: Claude --- .../com/azure/ai/agents/AgentBasicSample.java | 105 ++++++++++++++++++ .../ai/agents/AgentRetrieveBasicSample.java | 84 ++++++++++++++ 2 files changed, 189 insertions(+) create mode 100644 sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/AgentBasicSample.java create mode 100644 sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/AgentRetrieveBasicSample.java diff --git a/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/AgentBasicSample.java b/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/AgentBasicSample.java new file mode 100644 index 0000000000000..1d500057f5520 --- /dev/null +++ b/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/AgentBasicSample.java @@ -0,0 +1,105 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.ai.agents; + +import com.azure.ai.agents.models.AgentDetails; +import com.azure.ai.agents.models.AgentEndpointConfig; +import com.azure.ai.agents.models.AgentVersionDetails; +import com.azure.ai.agents.models.AzureCreateResponseOptions; +import com.azure.ai.agents.models.CreateAgentVersionInput; +import com.azure.ai.agents.models.FixedRatioVersionSelectionRule; +import com.azure.ai.agents.models.PromptAgentDefinition; +import com.azure.ai.agents.models.ProtocolConfiguration; +import com.azure.ai.agents.models.ResponsesProtocolConfiguration; +import com.azure.ai.agents.models.UpdateAgentDetailsOptions; +import com.azure.ai.agents.models.VersionSelector; +import com.azure.core.util.Configuration; +import com.azure.identity.DefaultAzureCredentialBuilder; +import com.openai.models.conversations.Conversation; +import com.openai.models.conversations.items.ItemCreateParams; +import com.openai.models.responses.EasyInputMessage; +import com.openai.models.responses.Response; +import com.openai.models.responses.ResponseCreateParams; +import com.openai.services.blocking.ConversationService; + +import java.util.Collections; + +/** + * Demonstrates synchronous prompt-agent creation, endpoint routing, and a multi-turn conversation. + * + *

Before running the sample, set these environment variables:

+ *
    + *
  • {@code FOUNDRY_PROJECT_ENDPOINT} - The Azure AI Project endpoint.
  • + *
  • {@code FOUNDRY_MODEL_NAME} - The model deployment name.
  • + *
+ */ +public class AgentBasicSample { + public static void main(String[] args) { + Configuration configuration = Configuration.getGlobalConfiguration(); + String endpoint = configuration.get("FOUNDRY_PROJECT_ENDPOINT"); + String model = configuration.get("FOUNDRY_MODEL_NAME"); + + AgentsClientBuilder builder = new AgentsClientBuilder() + .credential(new DefaultAzureCredentialBuilder().build()) + .endpoint(endpoint); + AgentsClient agentsClient = builder.buildAgentsClient(); + ResponsesClient responsesClient = builder.buildResponsesClient(); + ConversationService conversations = builder.buildOpenAIClient().conversations(); + + String agentName = "basic-agent"; + AgentVersionDetails agent = null; + AgentEndpointConfig originalEndpoint = null; + String conversationId = null; + + try { + agent = agentsClient.createAgentVersion(agentName, + new CreateAgentVersionInput(new PromptAgentDefinition(model) + .setInstructions("You are a helpful assistant that answers general questions."))); + System.out.printf("Agent created: %s (version %s)%n", agent.getName(), agent.getVersion()); + + AgentDetails details = agentsClient.getAgent(agentName); + originalEndpoint = details.getAgentEndpoint(); + AgentEndpointConfig endpointConfig = new AgentEndpointConfig() + .setVersionSelector(new VersionSelector().setVersionSelectionRules(Collections.singletonList( + new FixedRatioVersionSelectionRule(100).setAgentVersion(agent.getVersion())))) + .setProtocolConfiguration(new ProtocolConfiguration() + .setResponses(new ResponsesProtocolConfiguration())); + agentsClient.updateAgentDetails(agentName, + new UpdateAgentDetailsOptions().setAgentEndpoint(endpointConfig)); + System.out.printf("Agent endpoint configured for version %s%n", agent.getVersion()); + + Conversation conversation = conversations.create(); + conversationId = conversation.id(); + System.out.println("Conversation created: " + conversationId); + + AzureCreateResponseOptions options = new AzureCreateResponseOptions() + .setAgentReference(SampleUtils.toAgentReference(agent)); + Response first = responsesClient.createAzureResponse(options, + ResponseCreateParams.builder() + .conversation(conversationId) + .input("What is the size of France in square miles?")); + SampleUtils.printResponseText(first); + + conversations.items().create(ItemCreateParams.builder() + .conversationId(conversationId) + .addItem(EasyInputMessage.builder() + .role(EasyInputMessage.Role.USER) + .content("What is its capital city?") + .build()) + .build()); + Response second = responsesClient.createAzureResponse(options, + ResponseCreateParams.builder().conversation(conversationId)); + SampleUtils.printResponseText(second); + } finally { + if (conversationId != null) { + conversations.delete(conversationId); + } + if (agent != null) { + agentsClient.updateAgentDetails(agentName, + new UpdateAgentDetailsOptions().setAgentEndpoint(originalEndpoint)); + agentsClient.deleteAgentVersion(agentName, agent.getVersion()); + } + } + } +} diff --git a/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/AgentRetrieveBasicSample.java b/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/AgentRetrieveBasicSample.java new file mode 100644 index 0000000000000..112c54b1a60f5 --- /dev/null +++ b/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/AgentRetrieveBasicSample.java @@ -0,0 +1,84 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.ai.agents; + +import com.azure.ai.agents.models.AgentDetails; +import com.azure.ai.agents.models.AgentVersionDetails; +import com.azure.ai.agents.models.AzureCreateResponseOptions; +import com.azure.ai.agents.models.CreateAgentVersionInput; +import com.azure.ai.agents.models.PromptAgentDefinition; +import com.azure.core.util.Configuration; +import com.azure.identity.DefaultAzureCredentialBuilder; +import com.openai.models.conversations.Conversation; +import com.openai.models.conversations.items.ItemCreateParams; +import com.openai.models.responses.EasyInputMessage; +import com.openai.models.responses.Response; +import com.openai.models.responses.ResponseCreateParams; +import com.openai.services.blocking.ConversationService; + +/** + * Demonstrates synchronously retrieving an agent and conversation before creating a response. + * + *

Before running the sample, set these environment variables:

+ *
    + *
  • {@code FOUNDRY_PROJECT_ENDPOINT} - The Azure AI Project endpoint.
  • + *
  • {@code FOUNDRY_MODEL_NAME} - The model deployment name.
  • + *
+ */ +public class AgentRetrieveBasicSample { + public static void main(String[] args) { + Configuration configuration = Configuration.getGlobalConfiguration(); + String endpoint = configuration.get("FOUNDRY_PROJECT_ENDPOINT"); + String model = configuration.get("FOUNDRY_MODEL_NAME"); + + AgentsClientBuilder builder = new AgentsClientBuilder() + .credential(new DefaultAzureCredentialBuilder().build()) + .endpoint(endpoint); + AgentsClient agentsClient = builder.buildAgentsClient(); + ResponsesClient responsesClient = builder.buildResponsesClient(); + ConversationService conversations = builder.buildOpenAIClient().conversations(); + + String agentName = "retrieve-agent"; + AgentVersionDetails agent = null; + String conversationId = null; + + try { + agent = agentsClient.createAgentVersion(agentName, + new CreateAgentVersionInput(new PromptAgentDefinition(model) + .setInstructions("You are a helpful assistant."))); + + AgentDetails retrievedAgent = agentsClient.getAgent(agentName); + System.out.printf("Retrieved agent: %s (%s), latest version: %s%n", retrievedAgent.getName(), + retrievedAgent.getId(), retrievedAgent.getVersions().getLatest().getVersion()); + + Conversation conversation = conversations.create(); + conversationId = conversation.id(); + System.out.println("Conversation created: " + conversationId); + + Conversation retrievedConversation = conversations.retrieve(conversationId); + System.out.println("Retrieved conversation: " + retrievedConversation.id()); + + conversations.items().create(ItemCreateParams.builder() + .conversationId(conversationId) + .addItem(EasyInputMessage.builder() + .role(EasyInputMessage.Role.USER) + .content("How many feet are in a mile?") + .build()) + .build()); + System.out.println("Added a user message to the conversation"); + + Response response = responsesClient.createAzureResponse( + new AzureCreateResponseOptions().setAgentReference(SampleUtils.toAgentReference(agent)), + ResponseCreateParams.builder().conversation(conversationId)); + SampleUtils.printResponseText(response); + } finally { + if (conversationId != null) { + conversations.delete(conversationId); + } + if (agent != null) { + agentsClient.deleteAgentVersion(agentName, agent.getVersion()); + } + } + } +} From 0dfa871057733bdbcd5ae29c991ab92afdde7bd0 Mon Sep 17 00:00:00 2001 From: Mike Guo Date: Thu, 3 Sep 2026 09:53:03 +0800 Subject: [PATCH 3/4] fix AgentBasicAsyncSample error --- .../com/azure/ai/agents/AgentBasicAsyncSample.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/AgentBasicAsyncSample.java b/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/AgentBasicAsyncSample.java index 09602fb99b52c..fe9fe71c0ec33 100644 --- a/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/AgentBasicAsyncSample.java +++ b/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/AgentBasicAsyncSample.java @@ -71,7 +71,7 @@ public static void main(String[] args) { return agentsClient.updateAgentDetails(agentName, new UpdateAgentDetailsOptions().setAgentEndpoint(endpointConfig)); }) - .then(Mono.fromFuture(conversations.create())) + .then(Mono.defer(() -> Mono.fromFuture(conversations.create()))) .doOnNext(conversation -> { conversationIdRef.set(conversation.id()); System.out.println("Conversation created: " + conversation.id()); @@ -90,15 +90,15 @@ public static void main(String[] args) { .content("What is its capital city?") .build()) .build()))) - .then(responsesClient.createAzureResponse( + .then(Mono.defer(() -> responsesClient.createAzureResponse( new AzureCreateResponseOptions().setAgentReference( SampleUtils.toAgentReference(agentRef.get())), - ResponseCreateParams.builder().conversation(conversationIdRef.get()))) + ResponseCreateParams.builder().conversation(conversationIdRef.get())))) .doOnNext(SampleUtils::printResponseText) .then(); - sample.then(cleanup(agentsClient, conversations, agentName, agentRef, originalEndpointRef, - conversationIdRef)) + sample.then(Mono.defer(() -> cleanup(agentsClient, conversations, agentName, agentRef, originalEndpointRef, + conversationIdRef))) .onErrorResume(error -> cleanup(agentsClient, conversations, agentName, agentRef, originalEndpointRef, conversationIdRef) .then(Mono.error(error))) From a46fe4fc22d5cf24442f7f19f98a4fee961fa7d0 Mon Sep 17 00:00:00 2001 From: Mike Guo Date: Thu, 3 Sep 2026 12:20:01 +0800 Subject: [PATCH 4/4] fix error --- .../com/azure/ai/agents/AgentRetrieveBasicAsyncSample.java | 2 +- .../com/azure/ai/agents/WorkflowMultiAgentAsyncSample.java | 2 +- .../ai/agents/hostedagents/AgentEndpointAsyncSample.java | 4 ++-- .../ai/agents/hostedagents/SessionLogStreamAsyncSample.java | 4 ++-- .../ai/agents/toolboxes/ToolboxSearchAgentAsyncSample.java | 2 +- .../azure/ai/agents/tools/CodeInterpreterWithFilesAsync.java | 2 +- .../com/azure/ai/agents/tools/FileSearchStreamingAsync.java | 2 +- 7 files changed, 9 insertions(+), 9 deletions(-) diff --git a/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/AgentRetrieveBasicAsyncSample.java b/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/AgentRetrieveBasicAsyncSample.java index 9d76e209bc6e2..17ef95f7a3bf1 100644 --- a/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/AgentRetrieveBasicAsyncSample.java +++ b/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/AgentRetrieveBasicAsyncSample.java @@ -49,7 +49,7 @@ public static void main(String[] args) { .doOnNext(agentRef::set) .flatMap(agent -> agentsClient.getAgent(agent.getName())) .doOnNext(agent -> System.out.printf("Retrieved agent: %s (%s)%n", agent.getName(), agent.getId())) - .then(Mono.fromFuture(conversations.create())) + .then(Mono.defer(() -> Mono.fromFuture(conversations.create()))) .doOnNext(conversation -> conversationIdRef.set(conversation.id())) .flatMap(conversation -> Mono.fromFuture(conversations.retrieve(conversation.id()))) .doOnNext(conversation -> System.out.println("Retrieved conversation: " + conversation.id())) diff --git a/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/WorkflowMultiAgentAsyncSample.java b/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/WorkflowMultiAgentAsyncSample.java index 41120bb66251e..3aa7458fb44b4 100644 --- a/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/WorkflowMultiAgentAsyncSample.java +++ b/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/WorkflowMultiAgentAsyncSample.java @@ -57,7 +57,7 @@ public static void main(String[] args) { new AzureCreateResponseOptions().setAgentReference(SampleUtils.toAgentReference(workflow)), ResponseCreateParams.builder().input("What is 12 multiplied by 8?"))) .doOnNext(SampleUtils::printResponseText) - .then(cleanup(agentsClient, workflowRef, studentRef, teacherRef)) + .then(Mono.defer(() -> cleanup(agentsClient, workflowRef, studentRef, teacherRef))) .onErrorResume(error -> cleanup(agentsClient, workflowRef, studentRef, teacherRef) .then(Mono.error(error))) .block(); diff --git a/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/hostedagents/AgentEndpointAsyncSample.java b/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/hostedagents/AgentEndpointAsyncSample.java index 2c4f0a4b051eb..dade8be49d4a7 100644 --- a/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/hostedagents/AgentEndpointAsyncSample.java +++ b/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/hostedagents/AgentEndpointAsyncSample.java @@ -65,11 +65,11 @@ public static void main(String[] args) { new UpdateAgentDetailsOptions().setAgentEndpoint(endpointConfig)) .doOnNext(updated -> System.out.printf("Agent endpoint configured for agent: %s%n", updated.getName())) - .then(Mono.fromFuture(openAIAsyncClient.responses().create(ResponseCreateParams.builder() + .then(Mono.defer(() -> Mono.fromFuture(openAIAsyncClient.responses().create(ResponseCreateParams.builder() .input("What is the size of France in square miles?") .putAdditionalBodyProperty("agent_session_id", JsonValue.from(resources.getSession().getAgentSessionId())) - .build()))) + .build())))) .doOnNext(HostedAgentsSampleUtils::printResponseOutput) .then(); }); diff --git a/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/hostedagents/SessionLogStreamAsyncSample.java b/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/hostedagents/SessionLogStreamAsyncSample.java index 5e1b728dda4c7..822af913a3b91 100644 --- a/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/hostedagents/SessionLogStreamAsyncSample.java +++ b/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/hostedagents/SessionLogStreamAsyncSample.java @@ -66,11 +66,11 @@ public static void main(String[] args) { new UpdateAgentDetailsOptions().setAgentEndpoint(endpointConfig)) .doOnNext(updated -> System.out.printf("Agent endpoint configured for agent: %s%n", updated.getName())) - .then(Mono.fromFuture(openAIAsyncClient.responses().create(ResponseCreateParams.builder() + .then(Mono.defer(() -> Mono.fromFuture(openAIAsyncClient.responses().create(ResponseCreateParams.builder() .input("Say hello in one short sentence.") .putAdditionalBodyProperty("agent_session_id", JsonValue.from(resources.getSession().getAgentSessionId())) - .build()))) + .build())))) .doOnNext(HostedAgentsSampleUtils::printResponseOutput) .then(agentsAsyncClient.getSessionLogStreamWithResponse(agentName, resources.getAgent().getVersion(), resources.getSession().getAgentSessionId(), new RequestOptions())) diff --git a/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/toolboxes/ToolboxSearchAgentAsyncSample.java b/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/toolboxes/ToolboxSearchAgentAsyncSample.java index aeeeedc0f3f32..d90e266ccf721 100644 --- a/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/toolboxes/ToolboxSearchAgentAsyncSample.java +++ b/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/toolboxes/ToolboxSearchAgentAsyncSample.java @@ -81,7 +81,7 @@ public static void main(String[] args) { new AgentReference(agent.getName()).setVersion(agent.getVersion())), ResponseCreateParams.builder().input("What is my GitHub profile username?"))) .doOnNext(response -> System.out.println("Response: " + response.output())) - .then(cleanup(agentsClient, toolboxesClient, toolboxName, agentRef)) + .then(Mono.defer(() -> cleanup(agentsClient, toolboxesClient, toolboxName, agentRef))) .onErrorResume(error -> cleanup(agentsClient, toolboxesClient, toolboxName, agentRef) .then(Mono.error(error))) .block(); diff --git a/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/tools/CodeInterpreterWithFilesAsync.java b/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/tools/CodeInterpreterWithFilesAsync.java index c21b1ff6cc26f..596f073037ecb 100644 --- a/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/tools/CodeInterpreterWithFilesAsync.java +++ b/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/tools/CodeInterpreterWithFilesAsync.java @@ -70,7 +70,7 @@ public static void main(String[] args) { ToolSampleUtils.findContainerFile(response)); return response; })) - .then(cleanup(agentsClient, openAIClient, uploaded, csv, agentRef)) + .then(Mono.defer(() -> cleanup(agentsClient, openAIClient, uploaded, csv, agentRef))) .onErrorResume(error -> cleanup(agentsClient, openAIClient, uploaded, csv, agentRef) .then(Mono.error(error))) .block(); diff --git a/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/tools/FileSearchStreamingAsync.java b/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/tools/FileSearchStreamingAsync.java index e95cdf4d71e99..effafbba5d1bb 100644 --- a/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/tools/FileSearchStreamingAsync.java +++ b/sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/tools/FileSearchStreamingAsync.java @@ -81,7 +81,7 @@ public static void main(String[] args) { System.out.println(); SampleUtils.printResponseText(accumulator.response()); })) - .then(cleanup(agentsClient, openAIClient, agentRef, uploaded, vectorStore, document)) + .then(Mono.defer(() -> cleanup(agentsClient, openAIClient, agentRef, uploaded, vectorStore, document))) .onErrorResume(error -> cleanup(agentsClient, openAIClient, agentRef, uploaded, vectorStore, document) .then(Mono.error(error))) .block();