[Do not merge][Debug] Preview for voiceagent - #48484
[Do not merge][Debug] Preview for voiceagent#48484xitzhang (xitzhang) wants to merge 74 commits into
Conversation
|
Azure Pipelines: Successfully started running 1 pipeline(s). 10 pipeline(s) were filtered out due to trigger conditions. There may be pipelines that require an authorized user to comment /azp run to run. |
There was a problem hiding this comment.
Pull request overview
Warning
Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.
This PR introduces an initial preview of the azure-ai-voiceagents Python SDK, including generated management clients, a hand-written async realtime (WebSocket) client, and supporting samples/tests for validation and guidance.
Changes:
- Adds the new
azure-ai-voiceagentspackage skeleton (generated clients/models + packaging metadata). - Implements an async realtime WebSocket client (
aio/_realtime.py) and an aiohttp transport workaround for brotli negotiation. - Adds unit tests, recorded tests (with sanitization), live smoke test, and scenario-based samples + sample README.
Reviewed changes
Copilot reviewed 59 out of 63 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| sdk/voiceagents/azure-ai-voiceagents/tsp-location.yaml | Tracks source spec location for generation/regeneration. |
| sdk/voiceagents/azure-ai-voiceagents/tests/unit/test_configuration.py | Unit tests for configuration defaults/required params. |
| sdk/voiceagents/azure-ai-voiceagents/tests/unit/test_client_construction.py | Unit tests for sync/async client construction and operation groups. |
| sdk/voiceagents/azure-ai-voiceagents/tests/unit/test_brotli_workaround.py | Unit tests validating async brotli/aiohttp transport workaround behavior. |
| sdk/voiceagents/azure-ai-voiceagents/tests/unit/conftest.py | Disables recorded-test proxy startup for unit-test runs. |
| sdk/voiceagents/azure-ai-voiceagents/tests/recording/test_voice_agents_client_async.py | Recorded async functional tests for read-only operations. |
| sdk/voiceagents/azure-ai-voiceagents/tests/recording/test_voice_agents_client.py | Recorded sync functional tests for read-only operations. |
| sdk/voiceagents/azure-ai-voiceagents/tests/recording/conftest.py | Adds extra URI sanitization for project name in recordings. |
| sdk/voiceagents/azure-ai-voiceagents/tests/recording/_preparer.py | Shared preparer/sanitized env var loader for recorded/live tests. |
| sdk/voiceagents/azure-ai-voiceagents/tests/live/test_smoke_live.py | Always-live smoke test to catch auth/wire/serialization issues. |
| sdk/voiceagents/azure-ai-voiceagents/tests/live/conftest.py | Disables recorded-test proxy startup for live-test runs. |
| sdk/voiceagents/azure-ai-voiceagents/tests/conftest.py | Starts the test-proxy for recorded tests. |
| sdk/voiceagents/azure-ai-voiceagents/samples/quickstart/sample_quickstart_async.py | End-to-end quickstart: create temp agent + realtime audio session + cleanup. |
| sdk/voiceagents/azure-ai-voiceagents/samples/management/sample_read_conversation_audio.py | Demonstrates reading and streaming persisted conversation audio to WAV. |
| sdk/voiceagents/azure-ai-voiceagents/samples/management/sample_read_conversation.py | Demonstrates reading persisted conversation envelope/items/responses. |
| sdk/voiceagents/azure-ai-voiceagents/samples/management/sample_manage_voice_agent_versions.py | Demonstrates creating/listing/fetching voice agent versions (incl drafts). |
| sdk/voiceagents/azure-ai-voiceagents/samples/management/sample_generate_voice_agent.py | Demonstrates guided-authoring agent generation and cleanup. |
| sdk/voiceagents/azure-ai-voiceagents/samples/management/sample_create_voice_agent_with_tools.py | Demonstrates richer agent definitions (tools + audio config + BYOM). |
| sdk/voiceagents/azure-ai-voiceagents/samples/management/sample_create_and_manage_voice_agent_async.py | Async management lifecycle sample: create/get/list/delete. |
| sdk/voiceagents/azure-ai-voiceagents/samples/management/sample_create_and_manage_voice_agent.py | Sync management lifecycle sample incl update and enable/disable. |
| sdk/voiceagents/azure-ai-voiceagents/samples/live/sample_live_text_conversation_async.py | Live typed conversation + optional audio playback + readback. |
| sdk/voiceagents/azure-ai-voiceagents/samples/live/sample_live_audio_conversation_async.py | Live mic/speaker conversation + barge-in + readback. |
| sdk/voiceagents/azure-ai-voiceagents/samples/README.md | Scenario-organized samples index + setup/troubleshooting guidance. |
| sdk/voiceagents/azure-ai-voiceagents/pytest.ini | Configures pytest asyncio integration (asyncio_mode=auto). |
| sdk/voiceagents/azure-ai-voiceagents/pyrightconfig.json | Pyright configuration for local development/type-checking. |
| sdk/voiceagents/azure-ai-voiceagents/pyproject.toml | Package metadata and dependencies for azure-ai-voiceagents. |
| sdk/voiceagents/azure-ai-voiceagents/dev_requirements.txt | Dev dependencies including local azure-core/azure-identity and aiohttp. |
| sdk/voiceagents/azure-ai-voiceagents/azure_ai_voiceagents-1.0.0b1/azure/ai/voiceagents/_configuration.py | Snapshot of generated configuration under versioned folder. |
| sdk/voiceagents/azure-ai-voiceagents/azure/ai/voiceagents/py.typed | PEP 561 typing marker. |
| sdk/voiceagents/azure-ai-voiceagents/azure/ai/voiceagents/operations/_patch.py | Customization hook for generated operations (sync). |
| sdk/voiceagents/azure-ai-voiceagents/azure/ai/voiceagents/operations/init.py | Exposes sync operation groups and patch hook. |
| sdk/voiceagents/azure-ai-voiceagents/azure/ai/voiceagents/models/_patch.py | Customization hook for generated models. |
| sdk/voiceagents/azure-ai-voiceagents/azure/ai/voiceagents/models/_enums.py | Generated enums supporting voice agents + realtime schemas. |
| sdk/voiceagents/azure-ai-voiceagents/azure/ai/voiceagents/models/init.py | Public models/enums surface exports. |
| sdk/voiceagents/azure-ai-voiceagents/azure/ai/voiceagents/aio/operations/_patch.py | Customization hook for generated operations (async). |
| sdk/voiceagents/azure-ai-voiceagents/azure/ai/voiceagents/aio/operations/init.py | Exposes async operation groups and patch hook. |
| sdk/voiceagents/azure-ai-voiceagents/azure/ai/voiceagents/aio/_realtime.py | Hand-written async realtime WebSocket client and event typing. |
| sdk/voiceagents/azure-ai-voiceagents/azure/ai/voiceagents/aio/_patch.py | Async client patch: adds realtime namespace + brotli workaround. |
| sdk/voiceagents/azure-ai-voiceagents/azure/ai/voiceagents/aio/_configuration.py | Generated async configuration. |
| sdk/voiceagents/azure-ai-voiceagents/azure/ai/voiceagents/aio/_client.py | Generated async HTTP client and operation group wiring. |
| sdk/voiceagents/azure-ai-voiceagents/azure/ai/voiceagents/aio/init.py | Async package exports and patch hook. |
| sdk/voiceagents/azure-ai-voiceagents/azure/ai/voiceagents/_version.py | Package version constant. |
| sdk/voiceagents/azure-ai-voiceagents/azure/ai/voiceagents/_utils/init.py | Generated utils package init. |
| sdk/voiceagents/azure-ai-voiceagents/azure/ai/voiceagents/_unions.py | Generated public type aliases/unions. |
| sdk/voiceagents/azure-ai-voiceagents/azure/ai/voiceagents/_patch.py | Sync customization hook placeholder. |
| sdk/voiceagents/azure-ai-voiceagents/azure/ai/voiceagents/_configuration.py | Generated sync configuration. |
| sdk/voiceagents/azure-ai-voiceagents/azure/ai/voiceagents/_client.py | Generated sync HTTP client and operation group wiring. |
| sdk/voiceagents/azure-ai-voiceagents/azure/ai/voiceagents/init.py | Sync package exports and patch hook + version. |
| sdk/voiceagents/azure-ai-voiceagents/azure/ai/init.py | Namespace package marker for azure.ai. |
| sdk/voiceagents/azure-ai-voiceagents/azure/init.py | Namespace package marker for azure. |
| sdk/voiceagents/azure-ai-voiceagents/assets.json | Azure SDK assets tag configuration for CI. |
| sdk/voiceagents/azure-ai-voiceagents/apiview-properties.json | APIView cross-language mapping metadata. |
| sdk/voiceagents/azure-ai-voiceagents/_metadata.json | Tracks API version metadata for generation. |
| sdk/voiceagents/azure-ai-voiceagents/README.md | Package README scaffold. |
| sdk/voiceagents/azure-ai-voiceagents/MANIFEST.in | Source distribution inclusions (tests/samples/py.typed, etc.). |
| sdk/voiceagents/azure-ai-voiceagents/LICENSE | MIT license text. |
| sdk/voiceagents/azure-ai-voiceagents/CHANGELOG.md | Initial changelog entry for version b1. |
Suppressed comments (3)
sdk/voiceagents/azure-ai-voiceagents/tsp-location.yaml:1
- The PR description is still the template (missing a real summary of changes, links to relevant issues/spec PRs, and checklist updates). Please replace the placeholder description with a concise overview of what this PR adds (new SDK package, realtime WebSocket client, samples/tests) and link the spec change PR/commit referenced in
tsp-location.yaml.
sdk/voiceagents/azure-ai-voiceagents/tests/unit/test_configuration.py:1 - This test doesn’t actually validate overriding behavior because it passes the same value as the default (
\"v1\"). Consider changing it to pass a different value (orNoneif supported by the client) so the test can detect regressions where the override is ignored.
sdk/voiceagents/azure-ai-voiceagents/samples/management/sample_create_and_manage_voice_agent.py:1 - Using a fixed, non-namespaced
agent_namerisks collisions across repeated runs and (more importantly) can inadvertently delete/modify a real agent if a user already has an agent with that name. Safer pattern for samples is to generate a unique name (e.g., including a UUID suffix) and only delete the resource that the sample created.
There was a problem hiding this comment.
Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.
Note
This error may be related to your runner configuration. You can now configure runners for Copilot code review separately from Copilot cloud agent by creating a copilot-code-review.yml file with your setup steps. Read the docs for details.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 63 out of 67 changed files in this pull request and generated 2 comments.
Suppressed comments (8)
sdk/voiceagents/ci.yml:1
- The PR description is still the template text (it asks to 'add an informative description' and link issues), but the change set is substantial (new SDK + CI + tests + samples). Please update the PR description to summarize the intent/scope and link any relevant spec/issue/PR references so reviewers can validate the changes against requirements.
sdk/voiceagents/azure-ai-voiceagents/pyproject.toml:1 - Excluding the
azureandazure.aipackages from package discovery is likely to produce an invalid wheel where the namespace package initializers (azure/__init__.py,azure/ai/__init__.py) are not installed, breaking imports in environments that rely on pkgutil-style namespace packaging. Recommended fix: remove\"azure\"and\"azure.ai\"from the exclude list (or switch to an explicit include pattern forazure*packages consistent with other Azure SDK Python packages).
sdk/voiceagents/azure-ai-voiceagents/samples/live/sample_live_audio_conversation_async.py:1 - The callback can return fewer than
wantedbytes when it hits the end-of-stream marker (if not data: break). pyaudio callbacks must return exactly the requested frame size; otherwise playback can glitch or error. Instead ofbreak, pad the remainder with silence (or returnpaCompletewith a correctly-sized buffer) to satisfy pyaudio’s size contract.
sdk/voiceagents/azure-ai-voiceagents/tests/unit/test_configuration.py:1 - This test name claims the API version is 'overridden', but it sets the value to the default (
\"v1\"), so it doesn’t actually verify override behavior. Either rename the test to reflect what it asserts (e.g., thatapi_versionis stored), or setapi_versionto a non-default value (if supported by the client) to validate that overriding works.
sdk/voiceagents/azure-ai-voiceagents/azure/ai/voiceagents/aio/_realtime.py:648 - Passing the WebSocket subprotocol by manually setting
Sec-WebSocket-Protocolis fragile because many WS clients (including aiohttp) expect to manage/validate this header via theprotocols=[...]argument. Preferws_connect(..., protocols=[\"realtime\"], ...)and remove the manual header to avoid handshake issues and ensure the negotiated subprotocol is tracked correctly by the client.
headers: Dict[str, str] = {
"Authorization": f"Bearer {token.token}",
"Foundry-Features": str(foundry_features),
"Sec-WebSocket-Protocol": "realtime",
}
sdk/voiceagents/azure-ai-voiceagents/azure/ai/voiceagents/aio/_realtime.py:655
- Passing the WebSocket subprotocol by manually setting
Sec-WebSocket-Protocolis fragile because many WS clients (including aiohttp) expect to manage/validate this header via theprotocols=[...]argument. Preferws_connect(..., protocols=[\"realtime\"], ...)and remove the manual header to avoid handshake issues and ensure the negotiated subprotocol is tracked correctly by the client.
connection = await session.ws_connect(url, headers=headers, params=params, **self._kwargs)
sdk/voiceagents/azure-ai-voiceagents/azure/ai/voiceagents/_configuration.py:36
- The docstring says the default
api_versionisNone, but the implementation defaults it to\"v1\". Please align the docstring with the actual behavior (and consider applying the same fix in the async configuration file as well) to avoid confusing SDK consumers.
:keyword api_version: The API version to use for this operation. Known values are "v1" and
None. Default value is None. If not set, the operation's default API version will be used. Note
that overriding this default value may result in unsupported behavior.
sdk/voiceagents/azure-ai-voiceagents/azure/ai/voiceagents/_configuration.py:41
- The docstring says the default
api_versionisNone, but the implementation defaults it to\"v1\". Please align the docstring with the actual behavior (and consider applying the same fix in the async configuration file as well) to avoid confusing SDK consumers.
def __init__(self, endpoint: str, credential: "TokenCredential", **kwargs: Any) -> None:
api_version: str = kwargs.pop("api_version", "v1")
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 63 out of 67 changed files in this pull request and generated 3 comments.
Suppressed comments (6)
sdk/voiceagents/azure-ai-voiceagents/azure/ai/voiceagents/aio/_realtime.py:51
- The exported connection's public methods and attributes use
ClientEvent,ServerEvent,ConversationItem, and the resource helper classes in their annotations, but this module andaio/_patch.pyexport only the three connection classes. Consumers therefore see public annotations that can only be imported from the private_realtimemodule. Re-export all types used by this public surface fromazure.ai.voiceagents.aio.
__all__ = [
"AsyncRealtime",
"AsyncRealtimeConnection",
"AsyncRealtimeConnectionManager",
]
sdk/voiceagents/azure-ai-voiceagents/azure/ai/voiceagents/aio/_patch.py:45
- Constructing
aiohttp.ClientSessionhere eagerly requires a running event loop and binds the client to that loop. This makes otherwise valid async-client construction outsideasyncio.run()fail before any request. Set the defaultAccept-Encodingthrough the headers policy instead soAioHttpTransportcan retain its lazy session creation.
kwargs["transport"] = AioHttpTransport(
session=aiohttp.ClientSession(auto_decompress=False, headers={"Accept-Encoding": "gzip, deflate"})
)
sdk/voiceagents/azure-ai-voiceagents/samples/live/sample_live_audio_conversation_async.py:138
- Barge-in does not discard a partially consumed chunk: each callback copies bytes from
remainingbefore checking that chunk's sequence against_playback_base. A large delta can therefore continue playing across callbacks afterskip_pending_audio(). Track the sequence associated withremainingand invalidate it before slicing whenever it is older than the playback base.
sdk/voiceagents/azure-ai-voiceagents/samples/quickstart/sample_quickstart_async.py:100 - Barge-in does not discard a partially consumed chunk: bytes are copied from
remainingbefore the callback checks sequence validity. If a delta spans multiple callback buffers, stale audio continues playing afterskip_pending_audio(). Associateremainingwith its sequence and clear it before slicing when that sequence predates_playback_base.
sdk/voiceagents/azure-ai-voiceagents/tests/unit/test_configuration.py:47 - This test passes the same value as the default, so it still passes if the
api_versionoverride is ignored. Use a distinct sentinel value to verify that the keyword is actually honored.
sdk/voiceagents/azure-ai-voiceagents/samples/live/sample_live_text_conversation_async.py:179 - After a timeout, the response remains active and its eventual events stay queued. On the next turn,
pump()can consume the previous response'sresponse.doneand return before the new response completes, or the service can reject the new response while the old one is active. Cancel the timed-out response and drain/match its terminal event before accepting another prompt.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 63 out of 67 changed files in this pull request and generated 2 comments.
Suppressed comments (8)
sdk/voiceagents/azure-ai-voiceagents/pyproject.toml:13
- This new package is missing both
api.mdandapi.metadata.yml. The repository API consistency gate explicitly marks an affected package missing either file as a failure (.github/workflows/src/api-md-consistency/find_mismatches.js:76-92). Generate and commit both files withazpysdk apistub azure-ai-voiceagents.
sdk/voiceagents/azure-ai-voiceagents/azure/ai/voiceagents/aio/_patch.py:43 - Creating
aiohttp.ClientSessionin the client constructor makesVoiceAgentsClient(...)require a running event loop and binds the transport to that loop. This breaks the normal pattern of constructing an async SDK client beforeasyncio.run(...)(the new test even documents this limitation). Create/configure the session lazily when the transport opens instead.
kwargs["transport"] = AioHttpTransport(
session=aiohttp.ClientSession(auto_decompress=False, headers={"Accept-Encoding": "gzip, deflate"})
)
sdk/voiceagents/azure-ai-voiceagents/azure/ai/voiceagents/aio/_realtime.py:242
agent_nameis inserted into the URL as raw path text, unlike the generated operation which URL-serializes this path parameter. Reserved characters can therefore change the route instead of identifying the requested agent. Percent-encode the path segment before interpolation.
return f"{base}/agents/{agent_name}/endpoint/protocols/voice"
sdk/voiceagents/azure-ai-voiceagents/azure/ai/voiceagents/aio/_patch.py:65
- The public annotations/docstrings refer to
azure.ai.voiceagents.aio.ClientEventandServerEvent, but these aliases are defined only in the private_realtimemodule and are not re-exported here. Those documented imports therefore fail; include both aliases in the public aio surface.
__all__: list[str] = [
"VoiceAgentsClient",
"AsyncRealtime",
"AsyncRealtimeConnection",
"AsyncRealtimeConnectionManager",
] # Add all objects you want publicly available to users at this package level
sdk/voiceagents/azure-ai-voiceagents/samples/quickstart/sample_quickstart_async.py:115
- The callback consumes
remainingbefore checking its sequence against_playback_base. Afterskip_pending_audio()advances the base during barge-in, bytes left from the old response are still played on the next callback, so interruption does not actually drop all pending audio. Track the sequence associated withremainingand clear it before consuming stale bytes.
sdk/voiceagents/azure-ai-voiceagents/samples/live/sample_live_audio_conversation_async.py:153 - The callback consumes
remainingbefore checking its sequence against_playback_base. After barge-in advances the base, bytes left from the previous response are still played on the next callback, contradicting the promised immediate drop of pending audio. Track the sequence associated withremainingand discard stale bytes before playback.
sdk/voiceagents/azure-ai-voiceagents/tests/unit/test_configuration.py:47 - This test supplies the same
v1value as the default, so it still passes if the override is ignored. Use a non-default sentinel to exercise the behavior named by the test.
sdk/voiceagents/azure-ai-voiceagents/azure/ai/voiceagents/aio/_realtime.py:522 - The hand-written realtime implementation has no deterministic unit coverage for frame serialization/deserialization, typed-event mapping and unknown-event fallback, close/error handling, or resource helpers; the live smoke test covers only one service path. Add mocked WebSocket/session tests for these public behaviors so regressions do not require a gated live environment to detect.
async def recv(self) -> ServerEvent:
This comment has been minimized.
This comment has been minimized.
… identification fix - _realtime.py/aio: add User-Agent + x-ms-client-sdk identification (ports fix from azure-ai-voicelive PR #48848), with case-insensitive header collision guard so a caller-supplied extra_headers User-Agent (any casing) is not duplicated - test_realtime_client(_async).py: add regression tests for identification headers and case-insensitive override behavior - tests/agents/test_voice_agent_realtime_live(_async).py: new live-only tests for voice agent realtime session lifecycle, text-to-audio/transcript turns, and function tool-call round trip - tests/agents/test_voice_agent_conversations(_async).py: new recorded tests for beta.agent_endpoint_conversations REST surface, with a live-only setup step to obtain a sanitized conversation_id for playback - assets.json: pin new recordings via test-proxy push (tag ..._d354d861da) - test-resources.bicep/test-resources-post.ps1/tests.yml: new live-test CI infrastructure (Foundry account/project + gpt-realtime model deployment), following the azure-ai-voicelive package pattern - CHANGELOG.md: document the identification fix under Bugs Fixed Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
🟡 Changes recommended
Moderate issues remain in live-test provisioning, deployment configuration, and executable test coverage.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
Suppressed comments (16)
sdk/ai/azure-ai-projects/samples/agents/voice/sample_voice_agent_basic.py:24
- Voice-agent lifecycle APIs are introduced in 2.6.0, but this command permits 2.0.0. Raise the minimum so the models and operations used by the sample are present.
sdk/ai/azure-ai-projects/samples/agents/voice/sample_voice_agent_basic_async.py:18 - Voice-agent lifecycle APIs are introduced in 2.6.0, but this command permits 2.0.0. Raise the minimum so the async sample installs a compatible SDK.
sdk/ai/azure-ai-projects/samples/agents/voice/sample_voice_agent_generate.py:20 generate_agentfor voice agents is added in 2.6.0, so the current minimum can install an SDK that lacks the imported request type and method. Require 2.6.0.
sdk/ai/azure-ai-projects/samples/agents/voice/sample_voice_agent_live_function_tool.py:23- The realtime voice-agent API used by this sample is introduced in 2.6.0, but this command permits 2.0.0. Raise the minimum so
client.realtimeand the event models are available.
sdk/ai/azure-ai-projects/samples/agents/voice/sample_voice_agent_live_text_conversation.py:30 - The
realtimeextra andclient.realtimeAPI are introduced in 2.6.0, but this command accepts 2.0.0. Users following it can install an older SDK with no realtime namespace; require 2.6.0.
sdk/ai/azure-ai-projects/samples/agents/voice/sample_voice_agent_live_text_conversation_async.py:27 - The
realtimeextra and async realtime API are introduced in 2.6.0, but this command accepts 2.0.0. Require 2.6.0 so the documented install includes the APIs used below.
sdk/ai/azure-ai-projects/samples/agents/voice/sample_voice_agent_read_conversation.py:22 - The beta voice-conversation operation group is added in 2.6.0, so allowing 2.0.0 makes these instructions install versions without
beta.agent_endpoint_conversations. Raise the minimum to 2.6.0.
sdk/ai/azure-ai-projects/samples/agents/voice/sample_voice_agent_read_conversation_audio.py:26 - The beta voice-conversation audio APIs are added in 2.6.0, so allowing 2.0.0 can install an SDK without these methods. Raise the documented minimum to 2.6.0.
sdk/ai/azure-ai-projects/samples/agents/voice/sample_voice_agent_versions.py:19 - Voice-agent APIs are introduced in 2.6.0 (
CHANGELOG.md:3-17), but this command permits 2.0.0, whereVoiceAgentDefinitiondoes not provide this surface. Require 2.6.0 in the setup command.
sdk/ai/azure-ai-projects/samples/agents/voice/sample_voice_agent_with_tools.py:25 - Voice-agent APIs are introduced in 2.6.0 (
CHANGELOG.md:3-17), but this command permits 2.0.0, where these models are unavailable. Require 2.6.0 so the documented setup installs a compatible SDK.
sdk/ai/azure-ai-projects/tests/agents/test_voice_agent_crud.py:33 - This limitation contradicts the new
test_voice_agent_conversations.py, which creates a persisted realtime conversation ID and then reads it throughbeta.agent_endpoint_conversations. Update this note now that read-back is covered separately.
sdk/ai/azure-ai-projects/tests/agents/test_voice_agent_crud_async.py:34 - This limitation contradicts the new async conversation test, which creates a persisted realtime conversation ID and reads it through this REST operation group. Replace the stale service-bug note with a reference to the dedicated coverage.
sdk/ai/azure-ai-projects/tests/agents/test_voice_agent_telephony.py:34 - Every test in this new sync test module is unconditionally skipped, leaving the telephony and generated-audio operation behavior without executable functional coverage; the feature-header tests only validate header injection. Add transport-mocked tests or enable these scenarios before the API is considered covered.
sdk/ai/azure-ai-projects/tests/agents/test_voice_agent_telephony.py:62 - This says realtime-generated conversation IDs are rejected, but the new
test_voice_agent_conversations.pyexercises exactly that setup and read-back path. Update this bullet to point to the dedicated test instead of documenting a conflicting limitation.
sdk/ai/azure-ai-projects/tests/agents/test_voice_agent_telephony_async.py:34 - Every test in this new async test module is unconditionally skipped, leaving the async telephony and generated-audio operation behavior without executable functional coverage; the feature-header tests only validate header injection. Add transport-mocked tests or enable these scenarios before the API is considered covered.
sdk/ai/azure-ai-projects/tsp-location.yaml:2 - This regeneration pin changes the source specification, but the PR description is still the unfilled template and does not link the corresponding
azure-rest-api-specsPR. Replace the template with the voice-agent scope and the source-spec PR so reviewers can validate these generated API changes.
- Files reviewed: 75/79 changed files
- Comments generated: 6
- Review effort level: Balanced
| if (-not $ready) { | ||
| Write-Warning "The '$deploymentName' deployment did not finish provisioning in time. Live voice-agent tests may fail until it finishes." | ||
| } |
| TestResourceDirectories: | ||
| - ai/azure-ai-projects | ||
| EnvVars: | ||
| AZURE_TEST_RUN_LIVE: 'true' |
| agent_name=agent_name, | ||
| definition=VoiceAgentDefinition( | ||
| model_type=VoiceModelType.MANAGED, | ||
| model="gpt-realtime", |
| agent_name=agent_name, | ||
| definition=VoiceAgentDefinition( | ||
| model_type=VoiceModelType.MANAGED, | ||
| model="gpt-realtime", |
| # PR #48484: recording not yet available for these REST-only samples. | ||
| "sample_voice_agent_basic.py", | ||
| "sample_voice_agent_generate.py", | ||
| "sample_voice_agent_versions.py", | ||
| "sample_voice_agent_with_tools.py", |
| "sample_voice_agent_live_text_conversation_async.py", | ||
| "sample_voice_agent_live_audio_conversation_async.py", | ||
| # PR #48484: recording not yet available for this REST-only sample. | ||
| "sample_voice_agent_basic_async.py", |
…pupr Resolves conflicts in sdk/ai/azure-ai-projects: - CHANGELOG.md: combined voice-agent entries with main's content-safety moderation, ShellToolboxTool/WebIQPreviewTool, external_web_access, and sample-update entries (deduplicated bullets that existed on both sides); consolidated to a single Bugs Fixed section per release. - tests/foundry_features_header/foundry_features_header_test_base.py: the expected Foundry-Features value for .agents.* methods now includes BOTH VoiceAgents=V1Preview (ours) and ModelRouterControls=V1Preview (main's), matching the already-correctly-merged _AGENT_OPERATION_FEATURE_HEADERS constant in azure/ai/projects/models/_patch.py. - assets.json: our recordings tag and main's recordings tag had diverged (disjoint file sets - our 8 new voice-agent recordings vs main's 71 updated sample/telemetry recordings). Restored main's tag, layered our voice-agent recordings on top, and pushed a new combined tag so both sides' recorded tests have working cassettes. - tsp-location.yaml/.saved: main renamed this file to tsp-location.yaml.saved; followed main's convention for consistency. - api.metadata.yml, apiview-properties.json, docs/public-methods.md: kept ours for now; will be regenerated via �zpysdk apistub . immediately after this merge to reflect the fully-merged API surface. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…merge Follow-up to the merge commit: these are generated artifacts that were resolved with a placeholder during conflict resolution. Regenerated via `azpysdk apistub .` (api.md, apiview-properties.json - unchanged) and via a fresh runtime introspection of AIProjectClient (docs/public-methods.md) so they correctly reflect the fully-merged API surface (voice agents + main's independent additions). Also corrected api.metadata.yml's apiMdSha256, which the apistub tool did not update to match the regenerated api.md. Verified: full test suite passes (1038 passed, 119 skipped, 0 failed), including the foundry_features_header tests and the recorded voice-agent conversation tests against the newly-combined assets recordings tag. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
🟡 Changes recommended
Multiple moderate issues remain in sample execution, live-test configuration, realtime handling, and test coverage.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
Suppressed comments (9)
eng/tools/azure-sdk-tools/azpysdk/samples.py:96
- This ignore list still allows both read-conversation samples to run, although they unconditionally read
FOUNDRY_VOICE_CONVERSATION_IDand the new test resources cannot provide a pre-existing conversation ID. Becauseazpysdk samplesexecutes every unignored Python sample, the samples check will fail withKeyError; add both read samples to this list as the package-level sample tests already do.
"sample_voice_agent_live_audio_conversation_async.py",
scripts/devops_tasks/test_run_samples.py:98
- This legacy runner also executes every unignored Python sample, so the two read-conversation samples will fail when
FOUNDRY_VOICE_CONVERSATION_IDis absent. Keep this duplicate ignore table aligned with the azpysdk table and exclude both state-dependent samples.
"sample_voice_agent_live_audio_conversation_async.py",
sdk/ai/azure-ai-projects/samples/agents/voice/sample_voice_agent_read_conversation.py:29
- The included live conversation samples obtain the persisted ID from the
session.createdevent, notconversation.created. Referencing the latter sends users looking for an event this client intentionally leaves untyped; update the instruction to match the demonstrated API.
sdk/ai/azure-ai-projects/tests.yml:11 - This switches the entire
azure-ai-projectstest run to live mode, but the accompanying Bicep template explicitly provisions only the voice-agent surface and omits resources needed by the package's existing datasets, evaluations, fine-tuning, storage, and connection tests. The manual pipeline will therefore run unrelated recorded tests against an incomplete live environment; filter the test job to dedicated voice markers/tests or provision the complete package resource set.
sdk/ai/azure-ai-projects/tests/agents/test_voice_agent_realtime_live.py:222 - This live test bypasses the configured
FOUNDRY_VOICE_MODEL_NAMEused by the other tests in this class. Any test deployment whose resource name is not literallygpt-realtimewill fail before reaching the WebSocket behavior being tested. Use the prepared deployment name instead.
sdk/ai/azure-ai-projects/tests/agents/test_voice_agent_realtime_live_async.py:219 - The async function-tool test also hardcodes the model resource name instead of using the deployment supplied by
servicePreparer. This makes the test fail in otherwise valid environments that deploy the realtime model under another name.
sdk/ai/azure-ai-projects/tests/agents/test_voice_agent_telephony.py:82 - Every test in this new module is unconditionally skipped, so none of the telephony request paths, response deserialization, or generated-audio operations execute in CI. Header-proxy tests alone cannot validate those contracts; add transport-mocked/recorded coverage that can run now, and keep only genuinely service-dependent success scenarios skipped.
sdk/ai/azure-ai-projects/tests/samples/test_samples.py:409 - After
get_sample_pathsfilters out_async.py, this blacklist excludes every remaining voice sample, so this parametrized test has zero runnable cases. Commit recordings for at least the REST-only samples (or omit this no-op test until they are available) so the newly added samples receive actual execution coverage.
sdk/ai/azure-ai-projects/tests/samples/test_samples_async.py:328 - These three entries are all of the async voice samples discovered by
get_async_sample_paths, so this new test parametrizes no runnable case. Add a recording for the REST-only async sample (or defer this no-op test) rather than presenting an empty sample test suite.
- Files reviewed: 74/78 changed files
- Comments generated: 3
- Review effort level: Balanced
This comment has been minimized.
This comment has been minimized.
…e PR review comments CI fixes (build 6787439 Analyze stage): - cspell: add PSTN/pstn (telephony acronym) to the allowed words list - mypy/pyright: fix a genuine TypeSpec-emitter bug where the generated replace_telephony_transfer_targets JSON/IO[bytes]-body @overload stubs had etag/match_condition types swapped vs. the real implementation, in both sync and async _operations.py; also add a PostEmitter.ps1 fixup so this self-heals on future regenerations - pylint: fix C0411 wrong-import-order in _realtime.py/aio/_realtime.py (the UserAgentPolicy import was placed after local imports) PR #48484 review comment fixes: - Add the two persisted-conversation voice samples to IGNORED_SAMPLES in both sample-runner copies (they require a pre-existing FOUNDRY_VOICE_CONVERSATION_ID that automation doesn't provide) - test-resources-post.ps1: fail resource provisioning immediately on a deployment-readiness timeout instead of warning and continuing into live tests against a not-ready model - tests.yml: restrict the live-test pipeline to voice-specific tests via a new live_test_only marker + TestMarkArgument, since this pipeline's Bicep only provisions the voice model, not the full package's resource set - test_voice_agent_realtime_live(_async).py: use the prepared foundry_voice_model_name instead of a hardcoded "gpt-realtime" deployment name - _realtime.py: fix a real bug in the sync WebSocket connect -- disable websockets' own default User-Agent (user_agent_header=None) and drop a caller-supplied subprotocols kwarg to prevent a collision with the fixed "realtime" subprotocol, matching the async implementation's existing handling; add 2 regression tests - test_voice_agent_conversations(_async).py: make conversation completion a hard requirement for the audio assertions instead of a silent skip, so a future re-recording can't hide a regression in all four audio methods Verified: 1042 passed, 119 skipped, 0 failed. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
🟡 Changes recommended
Multiple moderate issues leave async error handling, model selection, and voice test coverage incomplete.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
Suppressed comments (5)
Previously missed (2) — in code that hasn't changed since the last review.
sdk/ai/azure-ai-projects/tests/agents/test_voice_agent_conversations.py:246
- This loop can fall through after every item returns 404, so the test passes without finding any item audio even though the preceding comment says per-item audio is a hard requirement. Use the loop's
elsebranch to fail when no iteration reachesbreak.
sdk/ai/azure-ai-projects/tests/agents/test_voice_agent_conversations_async.py:239 - This loop can exhaust all items after 404 responses and still let the test pass, leaving both per-item audio methods effectively unverified. Fail via a
for/elsewhen no item reaches thisbreak.
sdk/ai/azure-ai-projects/CHANGELOG.md:9
agentshas no publicupdatemethod (the generated surface exposescreate_versionandupdate_details), and voice-agent definitions are updated by creating a new immutable version. Referring users tocreate_version/updatetherefore points them to a nonexistent API.
* Added guided authoring via `project_client.agents.generate_agent(GenerateVoiceAgentRequest(kind=AgentKind.VOICE, ...))`, which returns a service-generated starter definition that can be edited afterward through the standard `create_version`/`update` flow.
sdk/ai/azure-ai-projects/samples/agents/voice/sample_voice_agent_read_conversation.py:29
- The referenced live-audio sample obtains the persisted ID from
RealtimeServerEventSessionCreated, not from aconversation.createdevent. The current instruction sends users looking for an event that the sample never handles.
sdk/ai/azure-ai-projects/tests/agents/test_voice_agent_telephony.py:82 - All three tests in this new module are unconditionally skipped, so CI never exercises telephony response deserialization, service error handling, or generated-audio error mapping. The header-capture suite stops at request transmission; please add transport-mocked or recorded response tests that can run until the service routes are available.
- Files reviewed: 75/79 changed files
- Comments generated: 2
- Review effort level: Balanced
…odel output sample_voice_agent_generate.py, sample_voice_agent_live_function_tool.py, sample_voice_agent_live_text_conversation(_async).py, and sample_voice_agent_live_audio_conversation_async.py print agent/LLM-generated text (instructions, transcripts) that can contain characters (curly quotes, em-dashes, etc.) outside some legacy, non-Unicode console encodings. When stdout isn't a real interactive console (for example piped or redirected on Windows, falling back to cp1252), a plain print() of that text can raise UnicodeEncodeError and crash the sample outright -- reproduced live in sample_voice_agent_live_audio_conversation_async.py. Fix is scoped locally to each risky print() call site via a small _safe_print helper (duplicated per file, matching this samples folder's existing convention of self-contained, standalone scripts): try a normal print() first, and only on UnicodeEncodeError fall back to replacing the unsupported characters. This is not a global sys.stdout.reconfigure(): this package's own sample_executor.py runs these samples in-process via exec_module() for tests/samples/test_samples.py, so any global stdout mutation at module level would leak into every other sample executed afterward in the same pytest worker. The local, per-call fallback has no effect outside its own print statement. Verified live against the real service, including re-running the exact sample/agent that originally crashed: no exception in any case, and zero data loss (verified via PYTHONUTF8=1, matching how a modern terminal behaves) when the console can actually represent the text; only the pathological legacy-codepage-without-a-real-console case substitutes a placeholder character instead of crashing. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Replace hardcoded agent names with FOUNDRY_VOICE_AGENT_NAME env var (sample_voice_agent_versions.py, sample_voice_agent_with_tools.py) - Use FOUNDRY_VOICE_MODEL env var instead of hardcoded model name (sample_voice_agent_live_function_tool.py) - Use typed isinstance checks instead of dict/getattr dual-path access for conversation items and tools, since the SDK deserializes these to real typed model instances, not raw mappings (sample_voice_agent_live_function_tool.py, sample_voice_agent_with_tools.py) - Remove unnecessary discriminator kwarg and its type: ignore suppression when constructing VoiceAgentMcpTool (sample_voice_agent_with_tools.py) - Update docstrings for the newly-documented environment variables All 3 changes verified end-to-end against the live service. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
🟡 Changes recommended
One critical live-test provisioning failure and three moderate preview/sample issues remain unresolved.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
Suppressed comments (2)
sdk/ai/azure-ai-projects/azure/ai/projects/_patch.py:265
- This top-level preview surface bypasses the client's documented
allow_previewopt-in:Realtime.connect()always sendsVoiceAgents=V1Preview, even when the client was constructed with the defaultallow_preview=False. Unlike.beta,client.realtimeis a top-level API, and every added sample passesallow_preview=True, so accessing it without that consent should fail rather than silently enabling preview behavior. Apply the same guard to the async property.
@property
def realtime(self) -> Realtime:
"""Realtime streaming entry point for voice agents.
:return: The realtime namespace, exposing ``connect(...)``.
:rtype: ~azure.ai.projects.Realtime
"""
if self._realtime is None:
self._realtime = Realtime(self)
sdk/ai/azure-ai-projects/azure/ai/projects/aio/_patch.py:210
- The async top-level realtime surface also silently opts callers into
VoiceAgents=V1Previeweven whenallow_previewis left at its documented default ofFalse. This differs from other top-level preview APIs and makes the explicit opt-in ineffective; mirror the sync guard here.
@property
def realtime(self) -> AsyncRealtime:
"""Realtime streaming entry point for voice agents.
:return: The realtime namespace, exposing ``connect(...)``.
:rtype: ~azure.ai.projects.aio.AsyncRealtime
"""
if self._realtime is None:
self._realtime = AsyncRealtime(self)
- Files reviewed: 75/79 changed files
- Comments generated: 2
- Review effort level: Balanced
| elif isinstance(event, RealtimeServerEventResponseDone): | ||
| # A response.done that isn't a function call is the final answer for this turn. | ||
| if not any( | ||
| isinstance(item, RealtimeConversationItemFunctionCall) for item in (event.response.output or []) | ||
| ): | ||
| return |
…safety check - tests.yml: add Location: eastus2. The shared archetype-sdk-tests template defaults this cloud's region to westus when unset, but gpt-realtime (GlobalStandard, 2025-08-28) is only deployable in eastus2/centralus/ canadacentral per the official Azure OpenAI region-availability docs, so resource provisioning was failing before any live test could run. - sample_voice_agent_live_function_tool.py: restore the dict/getattr dual-path check for identifying a function-call item in response.done's output list, in place of a pure isinstance() check introduced by an earlier cleanup pass. response.output is a documented open/extensible union; an item kind not yet mapped by this SDK version can surface as a plain mapping instead of a typed model, and the isinstance-only check would silently mistake the first tool-call turn for the final answer, closing the connection before the tool result is ever sent. Matches the same defensive pattern already used by the SDK's own test_voice_agent_realtime_live.py live test. Both changes verified: tests.yml against a live re-run of the affected pytest suite; the sample against a fresh live get_weather tool-call round-trip. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
🟡 Changes recommended
A critical bearer-token authentication defect and unresolved realtime sequencing, sample-runner, and test-coverage issues block approval.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
Suppressed comments (6)
Previously missed (4) — in code that hasn't changed since the last review.
sdk/ai/azure-ai-projects/tests/agents/test_voice_agent_conversations.py:226
- If every item returns 404, this loop falls through and the test passes without ever exercising
get_agent_conversation_item_audio_content, despite the preceding comment making per-item audio coverage a hard requirement. Track whether an item with audio was found and fail after the loop when none was found, so a cassette cannot silently omit this API coverage.
sdk/ai/azure-ai-projects/tests/agents/test_voice_agent_conversations_async.py:218 - If every item returns 404, this loop falls through and the test passes without ever exercising
get_agent_conversation_item_audio_content, despite the preceding comment making per-item audio coverage a hard requirement. Track whether an item with audio was found and fail after the loop when none was found, so a cassette cannot silently omit this API coverage.
sdk/ai/azure-ai-projects/tests/agents/test_voice_agent_realtime_live.py:267 - The follow-up response is created before the current function-call response emits
response.done. The realtime protocol can reject this as a concurrent active response; the existing Voice Live test waits forresponse.donebefore adding the tool output and callingresponse.create(sdk/voicelive/azure-ai-voicelive/tests/live/test_live_realtime_tools.py:271-281). Defer these operations until the function-call response completes.
sdk/ai/azure-ai-projects/tests/agents/test_voice_agent_realtime_live_async.py:265 - The follow-up response is created before the current function-call response emits
response.done. The realtime protocol can reject this as a concurrent active response; the existing Voice Live test waits forresponse.donebefore adding the tool output and callingresponse.create(sdk/voicelive/azure-ai-voicelive/tests/live/test_live_realtime_tools.py:271-281). Defer these operations until the function-call response completes.
sdk/ai/azure-ai-projects/tests/samples/test_samples.py:390
- This parameterization currently discovers no tests: every synchronous
sample_*.pyinsamples/agents/voiceis present insamples_to_skip. Pytest therefore reports only an empty-parameter skip, so this method provides no sample execution coverage. Add a recording for at least the REST-only samples (or remove the placeholder test until one is runnable).
sdk/ai/azure-ai-projects/tests/samples/test_samples_async.py:320 - This parameterization currently discovers no tests: all three asynchronous
sample_*.pyfiles insamples/agents/voiceare listed insamples_to_skip. The new test is therefore always an empty-parameter skip and exercises no sample. Add a recording for the REST-only basic sample (or remove the placeholder until it is runnable).
- Files reviewed: 75/79 changed files
- Comments generated: 4
- Review effort level: Balanced
| _args, kwargs = fake_session.ws_connect.call_args | ||
| assert _args[0].startswith("wss://my-account.services.ai.azure.com") | ||
| assert kwargs["params"]["api-version"] == "v1" | ||
| assert kwargs["headers"]["Authorization"] == "Bearer fake-token" |
| "sample_voice_agent_live_text_conversation_async.py", | ||
| # Runs until Ctrl-C (continuous microphone capture/playback); would hang indefinitely | ||
| # under this non-interactive runner whenever PyAudio and live credentials are available. | ||
| "sample_voice_agent_live_audio_conversation_async.py", |
| "sample_voice_agent_live_text_conversation_async.py", | ||
| # Runs until Ctrl-C (continuous microphone capture/playback); would hang indefinitely | ||
| # under this non-interactive runner whenever PyAudio and live credentials are available. | ||
| "sample_voice_agent_live_audio_conversation_async.py", |
| conn.conversation.item.create( | ||
| item=RealtimeConversationItemFunctionCallOutput(call_id=event.call_id, output=result) | ||
| ) | ||
| conn.response.create() |
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…pupr Resolves conflicts in api.metadata.yml, assets.json, and docs/public-methods.md. Since main already shipped 2.6.0 without Voice Agents, retargets Voice Agents work to a new 2.7.0b1 (beta) CHANGELOG section / package version instead of folding it into the already-released 2.6.0 section, leaving main's 2.6.0 CHANGELOG content and all prior versions untouched. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
🟡 Changes recommended
Preview opt-in, realtime response sequencing, and executable test-coverage issues remain unresolved.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
Suppressed comments (8)
sdk/ai/azure-ai-projects/CHANGELOG.md:9
.agents.update()was removed from this package; the existing changelog explicitly directs users tocreate_version()instead (CHANGELOG.md:406-407). Referring to anupdateflow here sends users to a nonexistent API.
* Added guided authoring via `project_client.agents.generate_agent(GenerateVoiceAgentRequest(kind=AgentKind.VOICE, ...))`, which returns a service-generated starter definition that can be edited afterward through the standard `create_version`/`update` flow.
sdk/ai/azure-ai-projects/azure/ai/projects/_patch.py:269
- This top-level preview entry point bypasses the client's explicit preview opt-in.
allow_previewdefaults toFalse, butRealtime.connect()defaultsfoundry_featurestoVoiceAgents=V1Previewand the connection manager always sends that header, soclient.realtime.connect(...)opts in even when the caller did not. Gate this property/connection onself._config.allow_preview, or omit the preview header unless the client was opted in.
@property
def realtime(self) -> Realtime:
"""Realtime streaming entry point for voice agents.
:return: The realtime namespace, exposing ``connect(...)``.
:rtype: ~azure.ai.projects.Realtime
"""
if self._realtime is None:
self._realtime = Realtime(self)
return self._realtime
sdk/ai/azure-ai-projects/azure/ai/projects/aio/_patch.py:215
- The async realtime entry point also bypasses the client's explicit preview opt-in:
AsyncRealtime.connect()suppliesVoiceAgents=V1Previewunconditionally, even whenallow_preview=False. Make the async path enforce the same opt-in contract as the other top-level preview APIs.
@property
def realtime(self) -> AsyncRealtime:
"""Realtime streaming entry point for voice agents.
:return: The realtime namespace, exposing ``connect(...)``.
:rtype: ~azure.ai.projects.aio.AsyncRealtime
"""
if self._realtime is None:
self._realtime = AsyncRealtime(self)
return self._realtime
sdk/ai/azure-ai-projects/samples/agents/voice/sample_voice_agent_live_function_tool.py:129
response.function_call_arguments.donecompletes the arguments, but the current response is still active until its subsequentresponse.done. Startingresponse.create()here can be rejected as a concurrent response; the repository's VoiceLive flow waits forresponse.donebefore adding the tool output and creating the follow-up response (sdk/voicelive/azure-ai-voicelive/tests/live/test_live_realtime_tools.py:271-281). Defer the output/follow-up until that event.
sdk/ai/azure-ai-projects/tests/agents/test_voice_agent_telephony.py:33- Every test in this new module is unconditionally skipped, so none of the advertised telephony, transfer-target, or generated-audio behavior is exercised in CI. Please add transport-mocked/recorded coverage for request serialization and response handling, or defer these tests until they can run.
sdk/ai/azure-ai-projects/tests/agents/test_voice_agent_telephony_async.py:33 - Every async test in this new module is unconditionally skipped, so the async telephony and generated-audio paths receive no executable coverage. Please add transport-mocked/recorded cases or defer these placeholders until they can run.
sdk/ai/azure-ai-projects/tests/samples/test_samples.py:409 - This blacklist excludes every synchronous
sample_*.pycurrently present undersamples/agents/voice, so parametrization produces no runnable cases and this test adds no sample coverage. Record the REST-only samples (or otherwise provide executable fixtures) and remove them from this list.
sdk/ai/azure-ai-projects/tests/samples/test_samples_async.py:328 - These entries, together with the WebSocket skip above, exclude all three asynchronous voice samples, leaving this parametrized test with zero runnable cases. Add a recording/fixture for the REST-only sample and stop skipping it so the async sample suite exercises at least one new sample.
- Files reviewed: 77/81 changed files
- Comments generated: 3
- Review effort level: Balanced
| conn.conversation.item.create( | ||
| item=RealtimeConversationItemFunctionCallOutput(call_id=event.call_id, output=result) | ||
| ) | ||
| conn.response.create() |
| await conn.conversation.item.create( | ||
| item=RealtimeConversationItemFunctionCallOutput(call_id=event.call_id, output=result) | ||
| ) | ||
| await conn.response.create() |
…e ignore lists, doc fix - Fix a concurrent-response race condition in the function-tool flow: sending response.create() right after FunctionCallArgumentsDone (before that turn's own response.done arrives) could race with the service and produce a concurrent-response error. Now the tool output is held and only submitted once response.done confirms the function-call turn has fully completed. Applied consistently to sample_voice_agent_live_function_tool.py and both the sync and async test_realtime_function_tool_call(_async) tests. Verified live (tests + sample pass end-to-end against the real service). - Add sample_voice_agent_live_function_tool.py to the azure-ai-projects ignore lists in scripts/devops_tasks/test_run_samples.py and eng/tools/azure-sdk-tools/azpysdk/samples.py (opens a live WebSocket connection, same reason the other realtime samples are already excluded there). - Fix sample_voice_agent_read_conversation.py's docstring: the persisted conversation id is captured from the session.created event, not conversation.created. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…e cassettes Resolves PR #48484 comments r3928375124/r3928375162: test_voice_samples collected zero test cases for the REST-only voice samples because they had no recorded cassette. - Recorded sample_voice_agent_basic/generate/versions/with_tools.py (sync) and sample_voice_agent_basic_async.py (async) live against the real service and pushed the new cassettes to a new azure-sdk-assets tag. - Removed these 5 files from samples_to_skip in test_samples.py/test_samples_async.py. - The LLM-judge content validation (validate_print_calls_by_llm) still needs a separate LLM_VALIDATION_PROJECT_ENDPOINT (gpt-5.2 judge deployment) not available here, so it's conditionally skipped for just these 5 samples -- mirroring the existing precedent for sample_agent_user_identity_isolation.py, where execution success is the validation signal. Their real REST calls are still recorded/replayed and exercised end-to-end. - Verified: recorded live, then replayed successfully with zero live credentials; full suite 1169 passed/0 failed (up from 1164, exactly +5, no regressions). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
🔵 Needs a closer look
Unresolved moderate issues affect public API usability, sample execution, documentation, and telephony test coverage.
Review details
Suppressed comments (8)
eng/tools/azure-sdk-tools/azpysdk/samples.py:93
- This ignore list omits
sample_voice_agent_live_function_tool.py. That sample also opens a realclient.realtimeWebSocket (and the package sample suite skips it for that reason), so the azpysdk sample check will execute it non-interactively and attempt a live connection. Add it beside the other realtime samples.
"sample_voice_agent_live_text_conversation.py",
"sample_voice_agent_live_text_conversation_async.py",
scripts/devops_tasks/test_run_samples.py:95
- This runner has the same missing exclusion for
sample_voice_agent_live_function_tool.py. Since that sample requires an uncaptured live WebSocket, running it in this non-interactive sample job will fail instead of exercising a replayable sample.
"sample_voice_agent_live_text_conversation.py",
"sample_voice_agent_live_text_conversation_async.py",
sdk/ai/azure-ai-projects/.env.template:27
- This comment is inaccurate:
foundry_voice_model_nameis also consumed by the conversation setup, telephony tests, and realtime live tests, not only the CRUD tests. Update it so contributors know which tests require this environment variable.
# Read by the recorded voice-agent CRUD tests only (tests/test_base.py), not by any sample.
FOUNDRY_VOICE_MODEL_NAME=
sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/init.py:22
- The async
VoiceAgentWebSocketOperationsexport has no correspondingAIProjectClient.voice_agent_web_socketattribute either, despite the generated class directing users there. Remove this unusable public surface or expose a functional async operation group consistently with the intendedasync_client.realtimeAPI.
from ._operations import VoiceAgentWebSocketOperations # type: ignore
sdk/ai/azure-ai-projects/azure/ai/projects/operations/init.py:22
VoiceAgentWebSocketOperationsis exported as public API, butAIProjectClientnever creates the documentedvoice_agent_web_socketattribute; the supported implementation isclient.realtime. Users following this generated operation class's documentation therefore getAttributeError. Hide this generated WebSocket operation group or wire it to a usable client surface consistently.
sdk/ai/azure-ai-projects/samples/agents/voice/sample_voice_agent_read_conversation.py:29- This instruction names a
conversation.createdevent, but the referenced live sample obtains the ID fromRealtimeServerEventSessionCreated(session.created). Following the current text leaves users waiting for an event this client does not handle; point them tosession.createdinstead.
sdk/ai/azure-ai-projects/tests/agents/test_voice_agent_telephony.py:37 - Every test in this newly added module is unconditionally skipped, so the sync telephony and generated-audio APIs receive no executable functional coverage. Before this feature is merged, add transport-mocked/recorded coverage that runs in CI (or enable these tests once the routes are available); placeholder tests cannot detect regressions.
sdk/ai/azure-ai-projects/tests/agents/test_voice_agent_telephony_async.py:37 - Every test in this newly added async module is unconditionally skipped, so the async telephony and generated-audio APIs receive no executable functional coverage. Before this feature is merged, add transport-mocked/recorded coverage that runs in CI (or enable these tests once the routes are available); placeholder tests cannot detect regressions.
- Files reviewed: 77/81 changed files
- Comments generated: 0 new
- Review effort level: Balanced
Regenerate from azure-rest-api-specs commit b538ac90619e094630e3c773d5231070809caf48 and wire preview headers for sync and async agent telephony operations. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
🔵 Needs a closer look
Broad preview SDK changes retain unresolved realtime URL-encoding and sample-correctness issues.
Review details
Suppressed comments (4)
sdk/ai/azure-ai-projects/docs/public-methods.md:15
- The new
agent_endpoint_conversationsandagent_telephonymethods are counted as stable solely because they are top-level, but this PR identifies voice agents as preview and gates these calls onVoiceAgents=V1Preview/allow_preview. Calling all 97 methods “stable” misstates their support status; updateGeneratePublicMethods.ps1to distinguish top-level placement from API stability, then regenerate this document.
sdk/ai/azure-ai-projects/samples/agents/voice/sample_voice_agent_live_text_conversation.py:30 - The setup command omits
python-dotenv, although this sample importsdotenvbelow. In a clean environment following these instructions, the sample fails before it can run.
sdk/ai/azure-ai-projects/samples/agents/voice/sample_voice_agent_live_text_conversation_async.py:27 - The setup command omits
python-dotenv, although this sample importsdotenvbelow. In a clean environment following these instructions, the sample fails before it can run.
sdk/ai/azure-ai-projects/samples/agents/voice/sample_voice_agent_read_conversation.py:29 - The referenced live-audio sample reads this ID from
RealtimeServerEventSessionCreated, whose wire event issession.created; it does not capture aconversation.createdevent. Naming the wrong event sends users looking for an event the example never handles.
- Files reviewed: 79/83 changed files
- Comments generated: 5
- Review effort level: Balanced
| base = endpoint.rstrip("/") | ||
| if base.startswith("https://"): | ||
| base = "wss://" + base[len("https://") :] | ||
| return f"{base}/agents/{agent_name}/endpoint/protocols/voice" |
| base = endpoint.rstrip("/") | ||
| if base.startswith("https://"): | ||
| base = "wss://" + base[len("https://") :] | ||
| return f"{base}/agents/{agent_name}/endpoint/protocols/voice" |
| project_client.agents.create_version( | ||
| agent_name=agent_name, | ||
| definition=VoiceAgentDefinition( | ||
| model_type=definition.model_type, # type: ignore[attr-defined] | ||
| model=definition.model, # type: ignore[attr-defined] | ||
| instructions=definition.instructions, # type: ignore[attr-defined] | ||
| store=True, | ||
| ), | ||
| ) |
| await project_client.agents.create_version( | ||
| agent_name=agent_name, | ||
| definition=VoiceAgentDefinition( | ||
| model_type=definition.model_type, # type: ignore[attr-defined] | ||
| model=definition.model, # type: ignore[attr-defined] | ||
| instructions=definition.instructions, # type: ignore[attr-defined] | ||
| store=True, | ||
| ), | ||
| ) |
| FOUNDRY_PROJECT_ENDPOINT= | ||
| FOUNDRY_PROJECT_API_KEY= | ||
| FOUNDRY_MODEL_NAME= | ||
| # Read by the recorded voice-agent CRUD tests only (tests/test_base.py), not by any sample. |
…for this SDK tsp-location.yaml is a local-only working file used with tsp-client update to point at a specific commit during generation. The Foundry SDK convention checks in tsp-location.yaml.saved instead, so remove the stray tracked copy introduced while regenerating from TypeSpec PR #45852. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…xitzhang/voice-agent-pupr # Conflicts: # sdk/ai/azure-ai-projects/tsp-location.yaml
There was a problem hiding this comment.
🔵 Needs a closer look
Unresolved public API consistency and test-coverage issues require changes and final human review.
Review details
Suppressed comments (15)
Previously missed (4) — in code that hasn't changed since the last review.
sdk/ai/azure-ai-projects/api.metadata.yml:2
- The updated API snapshot omits the new
agent_endpoint_conversationsandagent_telephonyattributes from both sync and asyncAIProjectClient, even though_client.pynow exposes them. Regenerateapi.mdand this metadata together so API review and consistency checks cover the actual client surface.
sdk/ai/azure-ai-projects/tests/agents/test_voice_agent_conversations.py:236 - If every item returns 404 for audio, this loop falls through and the test passes without ever exercising
get_agent_conversation_item_audio_content, despite the hard coverage requirement stated immediately above. Make absence of an audio-bearing item fail the test, for example with afor ... elseassertion.
sdk/ai/azure-ai-projects/tests/agents/test_voice_agent_conversations_async.py:239 - The async test also succeeds when every item has no audio, so
get_agent_conversation_item_audio_contentcan remain completely unexercised. Add a failure on loop exhaustion to ensure the claimed per-item audio coverage is real.
sdk/ai/azure-ai-projects/tests/agents/test_voice_agent_realtime_live_async.py:62 - These async live tests are not marked with
pytest.mark.asyncio, and this package does not enable pytest-asyncio auto mode. Pytest will therefore skip the coroutine tests instead of running them, so the new live pipeline only exercises the sync client. Add the asyncio marker at module or class level.
sdk/ai/azure-ai-projects/samples/agents/voice/sample_voice_agent_basic.py:24
- This voice-agent API is introduced in 2.7.0b1, but
>=2.0.0can install the 2.6.0 stable release, where these imports do not exist. Set the sample's minimum to 2.7.0b1.
sdk/ai/azure-ai-projects/samples/agents/voice/sample_voice_agent_basic_async.py:18 - This voice-agent API is introduced in 2.7.0b1, but
>=2.0.0can install the 2.6.0 stable release, where these imports do not exist. Set the sample's minimum to 2.7.0b1.
sdk/ai/azure-ai-projects/samples/agents/voice/sample_voice_agent_generate.py:20 - This voice-agent API is introduced in 2.7.0b1, but
>=2.0.0can install the 2.6.0 stable release, where these imports do not exist. Set the sample's minimum to 2.7.0b1.
sdk/ai/azure-ai-projects/samples/agents/voice/sample_voice_agent_live_function_tool.py:23 - The realtime extra and voice APIs are introduced in 2.7.0b1, but
>=2.0.0can install 2.6.0 stable, where this sample cannot run. Raise the minimum preview version.
sdk/ai/azure-ai-projects/samples/agents/voice/sample_voice_agent_live_text_conversation.py:30 - The realtime extra and voice APIs are introduced in 2.7.0b1, but
>=2.0.0can install 2.6.0 stable, where this sample cannot run. Raise the minimum preview version.
sdk/ai/azure-ai-projects/samples/agents/voice/sample_voice_agent_live_text_conversation_async.py:27 - The async realtime and voice APIs are introduced in 2.7.0b1, but
>=2.0.0can install 2.6.0 stable, where this sample cannot run. Raise the minimum preview version.
sdk/ai/azure-ai-projects/samples/agents/voice/sample_voice_agent_read_conversation.py:22 - This voice-agent API is introduced in 2.7.0b1, but
>=2.0.0can install the 2.6.0 stable release, where these methods do not exist. Set the sample's minimum to 2.7.0b1.
sdk/ai/azure-ai-projects/samples/agents/voice/sample_voice_agent_read_conversation_audio.py:26 - This voice-agent API is introduced in 2.7.0b1, but
>=2.0.0can install the 2.6.0 stable release, where these methods do not exist. Set the sample's minimum to 2.7.0b1.
sdk/ai/azure-ai-projects/samples/agents/voice/sample_voice_agent_versions.py:19 - This voice-agent API is introduced in 2.7.0b1, but
>=2.0.0can install the 2.6.0 stable release, where these imports do not exist. Set the sample's minimum to 2.7.0b1.
sdk/ai/azure-ai-projects/samples/agents/voice/sample_voice_agent_with_tools.py:25 - This voice-agent API is introduced in 2.7.0b1, but
>=2.0.0can install the 2.6.0 stable release, where these imports do not exist. Set the sample's minimum to 2.7.0b1.
sdk/ai/azure-ai-projects/tsp-location.yaml.saved:2 - This regeneration is pinned to the head of the still-open, blocked azure-rest-api-specs PR Azure/azure-rest-api-specs#45852, but this PR's description is still the untouched template and omits the required spec link and feature scope. Add that link and summarize the voice-agent batch-2 contract before this can be reviewed or merged as an SDK regeneration.
- Files reviewed: 78/82 changed files
- Comments generated: 2
- Review effort level: Balanced
| from ._operations import DatasetsOperations # type: ignore | ||
| from ._operations import DeploymentsOperations # type: ignore | ||
| from ._operations import IndexesOperations # type: ignore | ||
| from ._operations import VoiceAgentWebSocketOperations # type: ignore |
| from ._operations import DatasetsOperations # type: ignore | ||
| from ._operations import DeploymentsOperations # type: ignore | ||
| from ._operations import IndexesOperations # type: ignore | ||
| from ._operations import VoiceAgentWebSocketOperations # type: ignore |
…ampaigns (TypeSpec PR 45852 batch 2) - Added test_voice_agent_telephony_campaign.py/_async.py covering the new top-level agent_telephony operation group (call jobs and outbound calling campaigns), following the same skip-scaffolded pattern already used in test_voice_agent_telephony.py: not-found assertions for get/cancel_telephony_call_job, get/cancel/pause/resume_telephony_campaign, get_telephony_campaign_recipient_import, and get_telephony_operation. Marked skip because probing the live Voice Agents test resource returns 400 UnsupportedApiVersion for api-version 'v1' on these routes (route is registered, but not yet enabled for this SDK's API version) - un-skip once the service supports 'v1' for this operation group. - Updated CHANGELOG.md with a new bullet documenting the agent_telephony call-job/campaign surface (CreateTelephonyCallJobRequest, CreateTelephonyCampaignRequest, TelephonyCallJob, TelephonyCampaign, TelephonyOperation, TelephonyCampaignRecipientImport, and related LRO methods). - No production code changes needed: batch 2's TypeSpec delta is additive only (new agent_telephony operation group), existing agents.* telephony bindings/calls and Realtime protocol are untouched. No samples added, consistent with batch-1 telephony precedent and lack of real Teams Phone/Twilio provider credentials to exercise create_telephony_call_job/ create_telephony_campaign in this environment. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
🟡 Changes recommended
Moderate preview-contract, URL handling, and missing executable test-coverage issues remain unresolved.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
Suppressed comments (22)
Previously missed (3) — in code that hasn't changed since the last review.
sdk/ai/azure-ai-projects/azure/ai/projects/_realtime.py:863
- This preview API unconditionally defaults and sends
VoiceAgents=V1Preview, even when the parent client was constructed with the defaultallow_preview=False. That bypasses the explicit preview opt-in contract documented byAIProjectClientand README.md:45. Rejectconnect()unlessself._config.allow_previewis true (or otherwise avoid adding the preview header) and mirror the behavior in the async implementation.
sdk/ai/azure-ai-projects/azure/ai/projects/aio/_realtime.py:865 - The async realtime API has the same preview-opt-in bypass as the sync API: it always supplies
VoiceAgents=V1Previeweven when the parent client hasallow_preview=False. Enforce the parent's preview setting before returning the connection manager so the documented stable-client contract is consistent across both clients.
sdk/ai/azure-ai-projects/tests/agents/test_voice_agent_realtime_live_async.py:62 - These coroutine tests are not marked for an async pytest runner. Unlike
tests/agents/test_realtime_client_async.py, which setspytestmark = pytest.mark.asyncio, this live-only class has neither an asyncio marker norrecorded_by_proxy_async; in standard pytest-asyncio strict mode the methods are skipped instead of executed. Add the class-level marker so the new live coverage actually runs.
sdk/ai/azure-ai-projects/azure/ai/projects/_realtime.py:256
agent_nameis interpolated as a raw path segment, unlike the generated REST operations that percent-encode this parameter. Names containing reserved URL characters will alter the route or query rather than identify the intended agent. Encode the segment withurllib.parse.quote(agent_name, safe="")and add a reserved-character URL test.
base = endpoint.rstrip("/")
if base.startswith("https://"):
base = "wss://" + base[len("https://") :]
return f"{base}/agents/{agent_name}/endpoint/protocols/voice"
sdk/ai/azure-ai-projects/azure/ai/projects/aio/_realtime.py:270
- The async URL builder repeats the raw path interpolation from the sync client, so reserved characters in an agent name produce the wrong WebSocket route. Percent-encode
agent_nameas a single path segment and mirror the regression test in the async URL-helper tests.
base = endpoint.rstrip("/")
if base.startswith("https://"):
base = "wss://" + base[len("https://") :]
return f"{base}/agents/{agent_name}/endpoint/protocols/voice"
sdk/ai/azure-ai-projects/samples/agents/voice/sample_voice_agent_basic.py:24
- This command can install stable 2.6.x, which predates the voice-agent API. Require the preview version that introduces the sample.
sdk/ai/azure-ai-projects/samples/agents/voice/sample_voice_agent_basic_async.py:18 - This command can install stable 2.6.x, which predates the voice-agent API. Require the preview version that introduces the sample.
sdk/ai/azure-ai-projects/samples/agents/voice/sample_voice_agent_generate.py:20 - This command may install stable 2.6.x, which lacks
generate_agentand the voice-agent models. Require the first release containing this API.
sdk/ai/azure-ai-projects/samples/agents/voice/sample_voice_agent_live_audio_conversation_async.py:32 >=2.0.0can resolve stable 2.6.x and leave users without the new voice/realtime classes. Require 2.7.0b1 or newer explicitly.
sdk/ai/azure-ai-projects/samples/agents/voice/sample_voice_agent_live_function_tool.py:23>=2.0.0does not opt pip into prereleases, so this can install stable 2.6.x withoutclient.realtime. Require 2.7.0b1 or newer explicitly.
sdk/ai/azure-ai-projects/samples/agents/voice/sample_voice_agent_live_text_conversation.py:30>=2.0.0does not opt pip into prereleases, so this can install stable 2.6.x withoutclient.realtime. Require 2.7.0b1 or newer explicitly.
sdk/ai/azure-ai-projects/samples/agents/voice/sample_voice_agent_live_text_conversation_async.py:27- This can resolve stable 2.6.x, where the new async realtime surface is unavailable. Require the preview release that contains the sample.
sdk/ai/azure-ai-projects/samples/agents/voice/sample_voice_agent_read_conversation.py:22 - This command may install stable 2.6.x, which lacks
agent_endpoint_conversations. Require the first release containing this API.
sdk/ai/azure-ai-projects/samples/agents/voice/sample_voice_agent_read_conversation_audio.py:26 - This install command can resolve the current stable 2.6.x release, which does not contain the voice-agent APIs introduced in this PR, because pip excludes prereleases by default. Require the first release containing this sample so users do not immediately get import/attribute errors.
sdk/ai/azure-ai-projects/samples/agents/voice/sample_voice_agent_versions.py:19 - This command can install stable 2.6.x, which predates voice-agent versioning. Require the preview version that introduces these APIs.
sdk/ai/azure-ai-projects/samples/agents/voice/sample_voice_agent_with_tools.py:25 - This command may install stable 2.6.x, where the voice-agent models used below do not exist. Pin the minimum to the preview release that introduces this sample.
sdk/ai/azure-ai-projects/tests/agents/test_voice_agent_crud.py:31 - This note is contradicted by
test_voice_agent_conversations.py: that test obtains the ID from a live realtimesession.createdevent and successfully uses it withagent_endpoint_conversations. Remove the obsolete service-limitation claim so it does not discourage valid coverage or usage.
sdk/ai/azure-ai-projects/tests/agents/test_voice_agent_crud_async.py:32 - This note is contradicted by the async conversation test, which creates an ID through the realtime WebSocket and reads it through the REST conversation client. Remove this obsolete limitation.
sdk/ai/azure-ai-projects/tests/agents/test_voice_agent_telephony.py:62 - The new conversation tests now prove that a realtime
session.createdconversation ID works withagent_endpoint_conversations, so this stated limitation is stale and incorrectly justifies missing coverage here.
sdk/ai/azure-ai-projects/tests/agents/test_voice_agent_telephony.py:79 - All three tests in this new file are unconditionally skipped, so none of the added telephony binding/call/transfer behavior executes in CI. Add transport-mocked contract tests now (route, body, response/error handling), or defer exposing these APIs until runnable service coverage is available; header-only tests do not exercise this behavior.
sdk/ai/azure-ai-projects/tests/agents/test_voice_agent_telephony_async.py:62 - The async conversation test now obtains a realtime
session.createdID and successfully reads it through this REST operation group. Remove this obsolete service-limitation claim.
sdk/ai/azure-ai-projects/tests/agents/test_voice_agent_telephony_async.py:79 - Every async telephony test in this file is permanently skipped, leaving the newly added async binding/call/transfer paths unexecuted. Add transport-mocked contract tests or wait to expose the surface until runnable service coverage exists.
- Files reviewed: 80/84 changed files
- Comments generated: 2
- Review effort level: Balanced
|
|
||
| # To run only this test: | ||
| # pytest tests\agents\test_voice_agent_telephony_campaign.py::TestVoiceAgentTelephonyCampaign::test_telephony_call_job_not_found -s | ||
| @pytest.mark.skip( |
|
|
||
| # To run only this test: | ||
| # pytest tests\agents\test_voice_agent_telephony_campaign_async.py::TestVoiceAgentTelephonyCampaignAsync::test_telephony_call_job_not_found -s | ||
| @pytest.mark.skip( |
[Pilot] PR Pipeline Failure AnalysisWhat failedThe
The automated Relevant pipeline outputRecommended next steps
azsdk ci analyze https://github.com/Azure/azure-sdk-for-python/pull/48484 --output json
|
Description
Please add an informative description that covers that changes made by the pull request and link all relevant issues.
If an SDK is being regenerated based on a new API spec, a link to the pull request containing these API spec changes should be included above.
All SDK Contribution checklist:
General Guidelines and Best Practices
Testing Guidelines