Skip to content

Add realtime voice agent samples - #50410

Open
Mike Guo (guotuofeng) wants to merge 2 commits into
myguo/voice-websocket-testsfrom
myguo/voice-websocket-samples
Open

Add realtime voice agent samples#50410
Mike Guo (guotuofeng) wants to merge 2 commits into
myguo/voice-websocket-testsfrom
myguo/voice-websocket-samples

Conversation

@guotuofeng

@guotuofeng Mike Guo (guotuofeng) commented Sep 11, 2026

Copy link
Copy Markdown
Member

Description

Top PR in stack #50413, based on #50409. Adds focused synchronous and asynchronous voice-agent samples and links them from the Agents README.

Examples cover agent lifecycle and versions, generated definitions, tool configuration, persisted conversations/audio, realtime text, realtime audio, and client-side function tools.

Stack:

Validation: mvn -q -f sdk/ai/azure-ai-agents/pom.xml -DskipTests test-compile checkstyle:check

All SDK Contribution checklist:

General Guidelines and Best Practices

  • Title is clear and informative.
  • Changes are limited to focused samples and documentation.

Testing Guidelines

  • All samples compile against the complete stack.

Copilot AI commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

One or more custom setup steps configured for this repository failed during this Copilot code review run:

Install azsdk mcp server

Setup steps run before each review. If the review above is missing context, or no review was posted at all, the failing step above may be the cause. See the workflow run for failure details, fix your setup steps configuration, and re-request a review.

Note

You can configure setup steps for Copilot code review separately from Copilot cloud agent with a copilot-code-review.yml file. Read the docs for details.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

One or more issues must be addressed before approval.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

Adds preview voice-agent lifecycle, persistence, realtime text/audio, and function-tool samples for azure-ai-agents, with README documentation.

Changes:

  • Adds synchronous and asynchronous voice-agent samples.
  • Adds realtime WebSocket, audio playback, persistence, and tool examples.
  • Documents clients, configuration, and sample links in the README.
File summaries
File Description
sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/voice/VoiceAgentWithToolsSample.java Updated as part of this pull request.
sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/voice/VoiceAgentVersionsSample.java Updated as part of this pull request.
sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/voice/VoiceAgentSampleUtils.java Updated as part of this pull request.
sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/voice/VoiceAgentRealtimeSampleUtils.java Updated as part of this pull request.
sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/voice/VoiceAgentReadConversationSample.java Updated as part of this pull request.
sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/voice/VoiceAgentReadConversationAudioSample.java Updated as part of this pull request.
sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/voice/VoiceAgentLiveTextConversationSample.java Updated as part of this pull request.
sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/voice/VoiceAgentLiveTextConversationAsyncSample.java Updated as part of this pull request.
sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/voice/VoiceAgentLiveFunctionToolSample.java Updated as part of this pull request.
sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/voice/VoiceAgentLiveAudioConversationAsyncSample.java Updated as part of this pull request.
sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/voice/VoiceAgentGenerateSample.java Updated as part of this pull request.
sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/voice/VoiceAgentBasicSample.java Updated as part of this pull request.
sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/voice/VoiceAgentBasicAsyncSample.java Updated as part of this pull request.
sdk/ai/azure-ai-agents/README.md Updated as part of this pull request.
Review details

Suppressed comments (12)

sdk/ai/azure-ai-agents/README.md:961

  • This snippet only handles RealtimeServerEventResponseTextDelta, but the lifecycle samples create voice agents with audio output (and audio is the voice definition default). Following the documented setup therefore reaches response.done without printing the agent reply; either configure the example agent/response for TEXT or handle the audio/transcript events and document that requirement.
        if (event instanceof RealtimeServerEventResponseTextDelta) {
            System.out.print(((RealtimeServerEventResponseTextDelta) event).getDelta());

sdk/ai/azure-ai-agents/README.md:190

  • The paragraph above says every Beta*Client is built from AgentsClientBuilder.beta(), but BetaAgentEndpointConversationsClient is not exposed by that sub-builder; the actual API is builder.buildBetaAgentEndpointConversationsClient() (and the async equivalent). As written, this table sends readers to a nonexistent construction path for this newly documented client.
| `BetaAgentEndpointConversationsClient` | `VoiceAgents=V1Preview` |

sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/voice/VoiceAgentBasicAsyncSample.java:61

  • The normal and error cleanup paths delete the entire caller-selected agent, although createAgentVersion can add a version to an agent that already exists. Reusing FOUNDRY_VOICE_AGENT_NAME therefore destroys the existing agent instead of only cleaning up this sample's resources; use a unique/owned name or delete only resources created here.
            .then(client.deleteAgent(agentName))
            .onErrorResume(error -> client.deleteAgent(agentName)
                .onErrorResume(cleanupError -> Mono.empty())
                .then(Mono.error(error)))

sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/voice/VoiceAgentBasicSample.java:64

  • This cleanup deletes the entire agentName, but FOUNDRY_VOICE_AGENT_NAME is caller-configurable and createAgentVersion only adds a version to the named agent; it does not prove that the sample owns that agent. If the name already exists, the sample removes the caller's existing agent and all of its versions. Use a unique default/ownership check and only delete an agent created by this sample.
        } finally {
            client.deleteAgent(agentName);
            System.out.println("Deleted voice agent: " + agentName);

sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/voice/VoiceAgentLiveAudioConversationAsyncSample.java:164

  • This declaration is 122 characters long, exceeding the repository's 120-character Checkstyle limit, so the sample will fail the style check. Wrap the initializer onto the next line.
        private final AudioFormat format = new AudioFormat(VoiceAgentRealtimeSampleUtils.SAMPLE_RATE, 16, 1, true, false);

sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/voice/VoiceAgentLiveAudioConversationAsyncSample.java:261

  • The worker threads are only interrupted after the microphone and speaker have been stopped, closed, and nulled. A playback worker can already have taken a queued chunk and then execute speaker.write(...) against the closed/null field, producing an uncaught exception during normal shutdown; close() also returns without waiting for either worker to terminate. Stop and join the workers before releasing the lines, with safe visibility for the shared fields.
            if (captureThread != null) {
                captureThread.interrupt();
            }
            if (playbackThread != null) {
                playbackThread.interrupt();

sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/voice/VoiceAgentLiveAudioConversationAsyncSample.java:104

  • This error handler deletes agentName even for failures from generateAgent, before the pipeline has established that it owns that resource. With a caller-supplied name, a generation/auth/network failure can therefore delete an unrelated existing agent; track ownership after successful creation and skip cleanup unless this run created the agent.
            .then(Mono.defer(() -> cleanupAgent(agents, agentName, keepAgent)))
            .onErrorResume(error -> Mono.defer(() -> cleanupAgent(agents, agentName, keepAgent))
                .onErrorResume(cleanupError -> Mono.empty())
                .then(Mono.error(error)))

sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/voice/VoiceAgentLiveFunctionToolSample.java:113

  • This finally deletes the entire caller-selected agent even when createAgentVersion merely adds a version to an existing agent. A user who points FOUNDRY_VOICE_AGENT_NAME at an existing resource will lose that agent and its other versions; use a unique/owned name or clean up only the version created here.
        } finally {
            agents.deleteAgent(agentName);
            System.out.println("Deleted voice agent: " + agentName);

sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/voice/VoiceAgentLiveTextConversationAsyncSample.java:92

  • This error handler deletes agentName even for failures from generateAgent, before the pipeline has established that it owns that resource. With a caller-supplied name, a generation/auth/network failure can therefore delete an unrelated existing agent; track ownership after successful creation and skip cleanup unless this run created the agent.
            .then(Mono.defer(() -> cleanupAgent(agents, agentName, keepAgent)))
            .onErrorResume(error -> Mono.defer(() -> cleanupAgent(agents, agentName, keepAgent))
                .onErrorResume(cleanupError -> Mono.empty())
                .then(Mono.error(error)))

sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/voice/VoiceAgentRealtimeSampleUtils.java:124

  • SpeakerPlayer.play performs a potentially blocking SourceDataLine.write. In VoiceAgentLiveTextConversationAsyncSample, this method is called directly from the receiveEvents() subscriber, and the async WebSocket emits events from its Netty inbound callback. A stalled or slow speaker can therefore block the WebSocket event loop, preventing response frames such as response.done from being consumed and making the sample hang or overflow the session buffer. Move playback to a dedicated worker/queue or shift the subscriber off the event loop.
                line.write(pcm, 0, pcm.length);

sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/voice/VoiceAgentVersionsSample.java:66

  • This unconditional cleanup deletes the entire caller-selected agent, but createAgentVersion can add a version to an existing agent. If FOUNDRY_VOICE_AGENT_NAME points at an existing resource, the versions sample removes that resource and all of its other versions; use a unique/owned name or clean up only versions created by the sample.
        } finally {
            client.deleteAgent(agentName);

sdk/ai/azure-ai-agents/src/samples/java/com/azure/ai/agents/voice/VoiceAgentWithToolsSample.java:108

  • agentCreated only records that the version call succeeded; it does not establish that this sample created the agent. When the configured name already exists, this code adds a version and then deletes the pre-existing agent in finally. Use a unique/owned name or preserve existing agents and remove only the sample's version.
        } finally {
            if (agentCreated) {
                client.deleteAgent(agentName);
            }
  • Files reviewed: 14/14 changed files
  • Comments generated: 2
  • Review effort level: Lite

💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.

@guotuofeng
Mike Guo (guotuofeng) removed this pull request from stack #50411 September 11, 2026 01:13
@guotuofeng
Mike Guo (guotuofeng) added this pull request to stack #50413 September 11, 2026 01:13
@guotuofeng
Mike Guo (guotuofeng) force-pushed the myguo/voice-websocket-samples branch from cb05d1d to 766aa06 Compare September 11, 2026 01:13
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 278 pipeline(s).
36 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

1 similar comment
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 278 pipeline(s).
36 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

@guotuofeng
Mike Guo (guotuofeng) force-pushed the myguo/voice-websocket-samples branch from aab3a8e to 8d0c7dc Compare September 11, 2026 02:18
@guotuofeng
Mike Guo (guotuofeng) force-pushed the myguo/voice-websocket-samples branch from 8d0c7dc to 4425d7a Compare September 11, 2026 02:25
@guotuofeng
Mike Guo (guotuofeng) force-pushed the myguo/voice-websocket-samples branch from 4425d7a to 8bf6170 Compare September 11, 2026 02:29
@guotuofeng
Mike Guo (guotuofeng) force-pushed the myguo/voice-websocket-samples branch from 8bf6170 to c558890 Compare September 11, 2026 03:48
@guotuofeng
Mike Guo (guotuofeng) force-pushed the myguo/voice-websocket-samples branch from c558890 to eba1b01 Compare September 11, 2026 05:49
@guotuofeng
Mike Guo (guotuofeng) force-pushed the myguo/voice-websocket-samples branch from eba1b01 to 47aa527 Compare September 11, 2026 06:07
@guotuofeng
Mike Guo (guotuofeng) force-pushed the myguo/voice-websocket-samples branch from 47aa527 to 62cc033 Compare September 12, 2026 02:29
@guotuofeng
Mike Guo (guotuofeng) force-pushed the myguo/voice-websocket-samples branch 2 times, most recently from ea5191f to caca7ea Compare September 12, 2026 03:05
@guotuofeng
Mike Guo (guotuofeng) force-pushed the myguo/voice-websocket-samples branch from caca7ea to b482fb9 Compare September 12, 2026 03:14
@guotuofeng
Mike Guo (guotuofeng) force-pushed the myguo/voice-websocket-samples branch from b482fb9 to 34ece4b Compare September 12, 2026 04:03
@guotuofeng
Mike Guo (guotuofeng) force-pushed the myguo/voice-websocket-samples branch from 34ece4b to 32a7817 Compare September 12, 2026 04:14
@guotuofeng
Mike Guo (guotuofeng) force-pushed the myguo/voice-websocket-samples branch 2 times, most recently from 4ce5825 to 7f514e1 Compare September 12, 2026 04:31
@guotuofeng
Mike Guo (guotuofeng) force-pushed the myguo/voice-websocket-samples branch from 7f514e1 to 91fd3ef Compare September 12, 2026 06:43
@guotuofeng
Mike Guo (guotuofeng) force-pushed the myguo/voice-websocket-samples branch from 91fd3ef to b97bf92 Compare September 14, 2026 02:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants