Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,22 @@ resource managedNetwork 'Microsoft.CognitiveServices/accounts/managednetworks@20

// Outbound PE rules allow the managed VNet (where hosted agents run) to reach dependent resources
// Rules must be created sequentially to avoid conflicting state errors on the managed network

// The agent needs a PE back to the Foundry/AI Services endpoint itself (public access is disabled)
#disable-next-line BCP081
resource aiServicesOutboundRule 'Microsoft.CognitiveServices/accounts/managedNetworks/outboundRules@2025-10-01-preview' = {
parent: managedNetwork
name: 'aiservices-account-rule'
properties: {
type: 'PrivateEndpoint'
destination: {
serviceResourceId: aiAccount.id
subresourceTarget: 'account'
}
category: 'UserDefined'
}
}

#disable-next-line BCP081
resource storageOutboundRule 'Microsoft.CognitiveServices/accounts/managedNetworks/outboundRules@2025-10-01-preview' = {
parent: managedNetwork
Expand All @@ -51,6 +67,7 @@ resource storageOutboundRule 'Microsoft.CognitiveServices/accounts/managedNetwor
}
category: 'UserDefined'
}
dependsOn: [aiServicesOutboundRule]
}

#disable-next-line BCP081
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,39 @@ resource "azapi_resource" "managed_network" {
}
}

# Outbound PE rules allow the managed VNet (where hosted agents run) to reach
# dependent resources. The managed network API does not support concurrent
# outbound rule creation, so rules are serialized via depends_on to avoid
# "Resource is in conflicting state" errors. Chain order:
# aiservices -> storage -> cosmos -> search

# Managed Network Outbound Rule for the AI Services account itself
# The agent needs a PE back to the Foundry/AI Services endpoint because
# publicNetworkAccess is disabled on the account.
resource "azapi_resource" "aiservices_outbound_rule" {
type = "Microsoft.CognitiveServices/accounts/managedNetworks/outboundRules@2025-10-01-preview"
name = "aiservices-account-rule"
parent_id = azapi_resource.managed_network.id

schema_validation_enabled = false

body = {
properties = {
type = "PrivateEndpoint"
destination = {
serviceResourceId = azapi_resource.cognitive_account.id
subresourceTarget = "account"
}
category = "UserDefined"
}
}

depends_on = [
azapi_resource.managed_network,
azurerm_role_assignment.foundry_network_connection_approver
]
}

# Wait for Storage Account to be fully created before creating outbound rule
resource "time_sleep" "wait_storage" {
count = var.enable_storage ? 1 : 0
Expand Down Expand Up @@ -187,6 +220,7 @@ resource "azapi_resource" "storage_outbound_rule" {

depends_on = [
time_sleep.wait_storage,
azapi_resource.aiservices_outbound_rule,
azurerm_role_assignment.foundry_network_connection_approver,
azurerm_role_assignment.foundry_storage_blob,
azurerm_role_assignment.foundry_storage_contributor
Expand Down Expand Up @@ -288,6 +322,7 @@ resource "time_sleep" "wait_outbound_rules" {
create_duration = "600s"

depends_on = [
azapi_resource.aiservices_outbound_rule,
azapi_resource.storage_outbound_rule,
azapi_resource.cosmos_outbound_rule,
azapi_resource.aisearch_outbound_rule
Expand Down Expand Up @@ -330,6 +365,7 @@ resource "azapi_resource" "project_capability_host" {
time_sleep.wait_project_rbac,
# CRITICAL: All outbound rules must be created AND provisioned before capability host
# The capability host validates that outbound rules exist and are in Succeeded state
azapi_resource.aiservices_outbound_rule,
azapi_resource.storage_outbound_rule,
azapi_resource.cosmos_outbound_rule,
azapi_resource.aisearch_outbound_rule,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ resource "azapi_resource" "aisearch_outbound_rule" {

depends_on = [
time_sleep.wait_aisearch,
azapi_resource.cosmos_outbound_rule,
azurerm_role_assignment.foundry_network_connection_approver,
azurerm_role_assignment.project_search_index,
azurerm_role_assignment.project_search_contributor
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ resource "azapi_resource" "cosmos_outbound_rule" {

depends_on = [
time_sleep.wait_cosmos,
azapi_resource.storage_outbound_rule,
azurerm_role_assignment.foundry_network_connection_approver,
azurerm_role_assignment.foundry_cosmos_contributor,
azurerm_role_assignment.project_cosmos_reader,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
sys.exit(1)

# Azure VoiceLive SDK imports
from azure.ai.voicelive.aio import VoiceLiveConnection, connect, AgentSessionConfig
from azure.ai.voicelive.aio import VoiceLiveConnection, connect
from azure.ai.voicelive.models import (
AudioEchoCancellation,
AudioNoiseReduction,
Expand Down Expand Up @@ -245,7 +245,7 @@ def shutdown(self):

class AgentV2VoiceAssistant:
"""
Voice assistant using Azure AI Foundry agent with AgentSessionConfig.
Voice assistant using Azure AI Foundry agent.

This demonstrates the new pattern where the agent is configured at
connection time using AgentSessionConfig, rather than as a tool in the session.
Expand All @@ -255,11 +255,13 @@ def __init__(
self,
endpoint: str,
credential: AsyncTokenCredential,
agent_config: AgentSessionConfig,
agent_name: str,
project_name: str,
) -> None:
self.endpoint = endpoint
self.credential = credential
self.agent_config = agent_config
self.agent_name = agent_name
self.project_name = project_name
self.connection: Optional[VoiceLiveConnection] = None
self.audio_processor: Optional[AudioProcessor] = None
self.session_ready = False
Expand All @@ -269,15 +271,16 @@ async def start(self):
try:
logger.info(
"Connecting to VoiceLive API with agent %s for project %s",
self.agent_config.get("agent_name"),
self.agent_config.get("project_name"),
self.agent_name,
self.project_name,
)

# Connect using AgentSessionConfig
async with connect(
endpoint=self.endpoint,
credential=self.credential,
agent_config=self.agent_config, # Agent configured at connection time
agent_name=self.agent_name,
project_name=self.project_name,
) as connection:
conn = connection
self.connection = conn
Expand All @@ -295,8 +298,8 @@ async def start(self):
logger.info("Voice assistant ready! Start speaking...")
print("\n" + "=" * 60)
print("🎤 AGENT V2 VOICE ASSISTANT READY")
print(f"Agent: {self.agent_config.get('agent_name')}")
print(f"Project: {self.agent_config.get('project_name')}")
print(f"Agent: {self.agent_name}")
print(f"Project: {self.project_name}")
print("Start speaking to begin conversation")
print("Press Ctrl+C to exit")
print("=" * 60 + "\n")
Expand Down Expand Up @@ -424,18 +427,15 @@ async def _handle_event(self, event):

async def run_assistant(endpoint: str, agent_name: str, agent_project_name: str):
"""Run the voice assistant."""
agent_config: AgentSessionConfig = {
"agent_name": agent_name,
"project_name": agent_project_name,
}

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

assistant = AgentV2VoiceAssistant(
endpoint=endpoint,
credential=credential,
agent_config=agent_config,
agent_name=agent_name,
project_name=agent_project_name,
)

await assistant.start()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ Once the agent is deployed to Microsoft Foundry, you can interact with it using
- **Python 3.10+** with the following packages:

```bash
pip install azure-ai-voicelive[aiohttp]==1.2.0b5 azure-identity pyaudio
pip install azure-ai-voicelive[aiohttp]==1.3.0b1 azure-identity pyaudio
```

> [!NOTE]
Expand Down
Loading