Skip to content

Commit d3bb926

Browse files
Restore post-PR-#463 sample delta (manual cherry-pick from private) (#765)
Cherry-picks the customer-facing changes from private commits 7215e58..0e67b24 that have not yet flowed to public via the normal sync pipeline. The scheduled sync has been paused while we fix an architectural bug in the protected-paths guard (PR foundry-samples-pr#463) that surfaced tonight. See foundry-samples-pr#484 for the full diagnosis. Without this manual restoration, the following customer-facing changes from the private repo would not appear on public until the guard is fixed and sync resumes: - PR foundry-samples-pr#470 — Voice Live testing: update to use latest SDK (samples/python/hosted-agents/.../voicelive) - PR foundry-samples-pr#465 — fix: add outbound PE rule for AI Services account (managed VNet) (infrastructure-setup-bicep tf-18) - PR foundry-samples-pr#474 — fix(tf-18): add AI Services account outbound PE rule for managed VNet (infrastructure-setup-terraform tf-18) - PR foundry-samples-pr#250 — Hosted Agents validation pipeline parts that intersect samples/ (the rest is .azure-pipelines/, which is excluded from sync). This PR is scoped to the SIX files in the private 7215e58..0e67b24 delta that fall in the sync include-set (i.e., outside the sync exclude_pathspecs in .github/sync-config.json). All other changes in that range are excluded from sync (.github/, docs/, public-overlay/, .azure-pipelines/) and are not relevant to public. Spot-checks against the historical block-list show none of these files are validation-blocked. Authorship of the original changes is preserved in the PR body. The commit author is the operator performing the manual cherry-pick. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 550a914 commit d3bb926

6 files changed

Lines changed: 70 additions & 15 deletions

File tree

infrastructure/infrastructure-setup-bicep/18-managed-virtual-network/modules-network-secured/managed-network.bicep

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,22 @@ resource managedNetwork 'Microsoft.CognitiveServices/accounts/managednetworks@20
3939

4040
// Outbound PE rules allow the managed VNet (where hosted agents run) to reach dependent resources
4141
// Rules must be created sequentially to avoid conflicting state errors on the managed network
42+
43+
// The agent needs a PE back to the Foundry/AI Services endpoint itself (public access is disabled)
44+
#disable-next-line BCP081
45+
resource aiServicesOutboundRule 'Microsoft.CognitiveServices/accounts/managedNetworks/outboundRules@2025-10-01-preview' = {
46+
parent: managedNetwork
47+
name: 'aiservices-account-rule'
48+
properties: {
49+
type: 'PrivateEndpoint'
50+
destination: {
51+
serviceResourceId: aiAccount.id
52+
subresourceTarget: 'account'
53+
}
54+
category: 'UserDefined'
55+
}
56+
}
57+
4258
#disable-next-line BCP081
4359
resource storageOutboundRule 'Microsoft.CognitiveServices/accounts/managedNetworks/outboundRules@2025-10-01-preview' = {
4460
parent: managedNetwork
@@ -51,6 +67,7 @@ resource storageOutboundRule 'Microsoft.CognitiveServices/accounts/managedNetwor
5167
}
5268
category: 'UserDefined'
5369
}
70+
dependsOn: [aiServicesOutboundRule]
5471
}
5572

5673
#disable-next-line BCP081

infrastructure/infrastructure-setup-terraform/18-managed-virtual-network/ai-foundry.tf

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,39 @@ resource "azapi_resource" "managed_network" {
154154
}
155155
}
156156

157+
# Outbound PE rules allow the managed VNet (where hosted agents run) to reach
158+
# dependent resources. The managed network API does not support concurrent
159+
# outbound rule creation, so rules are serialized via depends_on to avoid
160+
# "Resource is in conflicting state" errors. Chain order:
161+
# aiservices -> storage -> cosmos -> search
162+
163+
# Managed Network Outbound Rule for the AI Services account itself
164+
# The agent needs a PE back to the Foundry/AI Services endpoint because
165+
# publicNetworkAccess is disabled on the account.
166+
resource "azapi_resource" "aiservices_outbound_rule" {
167+
type = "Microsoft.CognitiveServices/accounts/managedNetworks/outboundRules@2025-10-01-preview"
168+
name = "aiservices-account-rule"
169+
parent_id = azapi_resource.managed_network.id
170+
171+
schema_validation_enabled = false
172+
173+
body = {
174+
properties = {
175+
type = "PrivateEndpoint"
176+
destination = {
177+
serviceResourceId = azapi_resource.cognitive_account.id
178+
subresourceTarget = "account"
179+
}
180+
category = "UserDefined"
181+
}
182+
}
183+
184+
depends_on = [
185+
azapi_resource.managed_network,
186+
azurerm_role_assignment.foundry_network_connection_approver
187+
]
188+
}
189+
157190
# Wait for Storage Account to be fully created before creating outbound rule
158191
resource "time_sleep" "wait_storage" {
159192
count = var.enable_storage ? 1 : 0
@@ -187,6 +220,7 @@ resource "azapi_resource" "storage_outbound_rule" {
187220

188221
depends_on = [
189222
time_sleep.wait_storage,
223+
azapi_resource.aiservices_outbound_rule,
190224
azurerm_role_assignment.foundry_network_connection_approver,
191225
azurerm_role_assignment.foundry_storage_blob,
192226
azurerm_role_assignment.foundry_storage_contributor
@@ -288,6 +322,7 @@ resource "time_sleep" "wait_outbound_rules" {
288322
create_duration = "600s"
289323

290324
depends_on = [
325+
azapi_resource.aiservices_outbound_rule,
291326
azapi_resource.storage_outbound_rule,
292327
azapi_resource.cosmos_outbound_rule,
293328
azapi_resource.aisearch_outbound_rule
@@ -330,6 +365,7 @@ resource "azapi_resource" "project_capability_host" {
330365
time_sleep.wait_project_rbac,
331366
# CRITICAL: All outbound rules must be created AND provisioned before capability host
332367
# The capability host validates that outbound rules exist and are in Succeeded state
368+
azapi_resource.aiservices_outbound_rule,
333369
azapi_resource.storage_outbound_rule,
334370
azapi_resource.cosmos_outbound_rule,
335371
azapi_resource.aisearch_outbound_rule,

infrastructure/infrastructure-setup-terraform/18-managed-virtual-network/aisearch.tf

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ resource "azapi_resource" "aisearch_outbound_rule" {
8585

8686
depends_on = [
8787
time_sleep.wait_aisearch,
88+
azapi_resource.cosmos_outbound_rule,
8889
azurerm_role_assignment.foundry_network_connection_approver,
8990
azurerm_role_assignment.project_search_index,
9091
azurerm_role_assignment.project_search_contributor

infrastructure/infrastructure-setup-terraform/18-managed-virtual-network/cosmos.tf

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ resource "azapi_resource" "cosmos_outbound_rule" {
9999

100100
depends_on = [
101101
time_sleep.wait_cosmos,
102+
azapi_resource.storage_outbound_rule,
102103
azurerm_role_assignment.foundry_network_connection_approver,
103104
azurerm_role_assignment.foundry_cosmos_contributor,
104105
azurerm_role_assignment.project_cosmos_reader,

samples/python/hosted-agents/bring-your-own/voicelive/client/voicelive_client.py

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@
4545
sys.exit(1)
4646

4747
# Azure VoiceLive SDK imports
48-
from azure.ai.voicelive.aio import VoiceLiveConnection, connect, AgentSessionConfig
48+
from azure.ai.voicelive.aio import VoiceLiveConnection, connect
4949
from azure.ai.voicelive.models import (
5050
AudioEchoCancellation,
5151
AudioNoiseReduction,
@@ -245,7 +245,7 @@ def shutdown(self):
245245

246246
class AgentV2VoiceAssistant:
247247
"""
248-
Voice assistant using Azure AI Foundry agent with AgentSessionConfig.
248+
Voice assistant using Azure AI Foundry agent.
249249
250250
This demonstrates the new pattern where the agent is configured at
251251
connection time using AgentSessionConfig, rather than as a tool in the session.
@@ -255,11 +255,13 @@ def __init__(
255255
self,
256256
endpoint: str,
257257
credential: AsyncTokenCredential,
258-
agent_config: AgentSessionConfig,
258+
agent_name: str,
259+
project_name: str,
259260
) -> None:
260261
self.endpoint = endpoint
261262
self.credential = credential
262-
self.agent_config = agent_config
263+
self.agent_name = agent_name
264+
self.project_name = project_name
263265
self.connection: Optional[VoiceLiveConnection] = None
264266
self.audio_processor: Optional[AudioProcessor] = None
265267
self.session_ready = False
@@ -269,15 +271,16 @@ async def start(self):
269271
try:
270272
logger.info(
271273
"Connecting to VoiceLive API with agent %s for project %s",
272-
self.agent_config.get("agent_name"),
273-
self.agent_config.get("project_name"),
274+
self.agent_name,
275+
self.project_name,
274276
)
275277

276278
# Connect using AgentSessionConfig
277279
async with connect(
278280
endpoint=self.endpoint,
279281
credential=self.credential,
280-
agent_config=self.agent_config, # Agent configured at connection time
282+
agent_name=self.agent_name,
283+
project_name=self.project_name,
281284
) as connection:
282285
conn = connection
283286
self.connection = conn
@@ -295,8 +298,8 @@ async def start(self):
295298
logger.info("Voice assistant ready! Start speaking...")
296299
print("\n" + "=" * 60)
297300
print("🎤 AGENT V2 VOICE ASSISTANT READY")
298-
print(f"Agent: {self.agent_config.get('agent_name')}")
299-
print(f"Project: {self.agent_config.get('project_name')}")
301+
print(f"Agent: {self.agent_name}")
302+
print(f"Project: {self.project_name}")
300303
print("Start speaking to begin conversation")
301304
print("Press Ctrl+C to exit")
302305
print("=" * 60 + "\n")
@@ -424,18 +427,15 @@ async def _handle_event(self, event):
424427

425428
async def run_assistant(endpoint: str, agent_name: str, agent_project_name: str):
426429
"""Run the voice assistant."""
427-
agent_config: AgentSessionConfig = {
428-
"agent_name": agent_name,
429-
"project_name": agent_project_name,
430-
}
431430

432431
credential: AsyncTokenCredential = DefaultAzureCredential()
433432
logger.info("Using DefaultAzureCredential")
434433

435434
assistant = AgentV2VoiceAssistant(
436435
endpoint=endpoint,
437436
credential=credential,
438-
agent_config=agent_config,
437+
agent_name=agent_name,
438+
project_name=agent_project_name,
439439
)
440440

441441
await assistant.start()

samples/python/hosted-agents/bring-your-own/voicelive/hello-world-invocations-voicelive/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,7 @@ Once the agent is deployed to Microsoft Foundry, you can interact with it using
226226
- **Python 3.10+** with the following packages:
227227

228228
```bash
229-
pip install azure-ai-voicelive[aiohttp]==1.2.0b5 azure-identity pyaudio
229+
pip install azure-ai-voicelive[aiohttp]==1.3.0b1 azure-identity pyaudio
230230
```
231231

232232
> [!NOTE]

0 commit comments

Comments
 (0)