Sr. Content Developer at Microsoft, working remotely in PA, TechBash conference organizer, former Microsoft MVP, Husband, Dad and Geek.
160690 stories
·
33 followers

Your Private Endpoint Does Not Cover Agent Egress: Locking Down Azure AI Foundry in Both Directions

1 Share

I got asked a question a few weeks ago that sounded simple. Lock down Azure AI Foundry with Private Link, and let the agents call an MCP server hosted on a VM in a different virtual network. Everything private, nothing over the public internet, and proof rather than a diagram.

I said yes, that is straightforward. I was wrong about the second half.

What follows is what I actually built, what broke along the way, and the one thing that cost me most of an afternoon. Everything here was deployed and verified on a live subscription. The scripts and the raw logs are on GitHub if you want to reproduce it:

GitHub - raffaeu/azure-foundry-private-link: Provision Azure AI Foundry behind a Private Endpoint with private DNS, then prove it works: HTTP 200 from inside the VNet over a private IP, HTTP 403 from outside. Numbered az CLI scripts, managed-identity auth, and a one-command test harness.

Provision Azure AI Foundry behind a Private Endpoint with private DNS, then prove it works: HTTP 200 from inside the VNet over a private IP, HTTP 403 from outside. Numbered az CLI scripts, managed-identity auth, and a one-command test harness. - raffaeu/azure-foundry-private-link

github.com

The mistake almost everyone makes

Here is the thing I want you to take away, even if you read nothing else.

A private endpoint on your Foundry resource secures traffic going into Foundry. It does absolutely nothing about the traffic your agents send out.

Those are two different features, configured in two completely different ways, with two completely different sets of failure modes. If you configure the private endpoint, disable public network access, see your test call succeed from inside the VNet, and call it done, you have secured exactly half of the problem. Your agent can still be reaching out to a tool endpoint from a Microsoft owned public IP address, and nothing in the portal will tell you that.

 DirectionMechanism
Inboundclient to FoundryPrivate endpoint
OutboundFoundry agent to your APINetwork injection

I built and proved both. Let me take them in order.

Inbound vs Outbound traffic via Private Link.

Part one: the inbound half

This part is well documented and mostly behaves. The shape of it:

  • A VNet with a subnet for private endpoints, with private-endpoint-network-policies disabled, which is mandatory and easy to forget
  • A Foundry account created with a custom domain, which is required for Private Link to work at all
  • Private DNS zones, and here is the first trap
  • A private endpoint with a DNS zone group
  • publicNetworkAccess set to Disabled

The DNS trap is that you need three zones, not one:

privatelink.openai.azure.com
privatelink.services.ai.azure.com
privatelink.cognitiveservices.azure.com

The Foundry endpoint answers on several FQDNs, and if you only create the zone matching the hostname you happen to be testing with, the others silently resolve to the public IP. Traffic still works, which is exactly what makes it dangerous. It looks fine.

All three zones have to be linked to the VNet, and the DNS zone group on the private endpoint is what actually writes the A records. Miss the zone group and you get zones with nothing in them.

Proving it, properly

This is where I want to push back on how these setups usually get validated. People run one test, from inside the VNet, see a 200, and declare victory.

That proves nothing on its own.

A 200 from inside the VNet is perfectly compatible with a resource whose public endpoint is also wide open. You need both halves:

from inside the VNet   ->  DNS resolves to 10.0.1.5, call returns HTTP 200
from my laptop         ->  HTTP 403, "Public access is disabled"

Only when both are true have you proven anything. I wrote a small script that runs both and prints a single verdict, because I know that if it is two commands, one of them eventually gets skipped.

iTerm TEST proving the inbound and outbound traffic.

A couple of things bit me here that are worth knowing.

The tenant had key based authentication disabled by policy. Every api-key call returned 403 AuthenticationTypeDisabled, and attempts to set disableLocalAuth back to false silently reverted. Not a problem, Entra ID tokens are the better practice anyway, but if you are following an older tutorial with api-key headers you will lose time wondering why your key is rejected. Inside the VM I used the managed identity via IMDS, so there were no secrets to copy around at all.

The subscription also blocked public IP creation, which meant Azure Bastion could not be deployed. That turned out not to matter. az vm run-command invoke runs scripts inside a VM over the Azure control plane, no SSH, no public IP, no Bastion required. I now prefer it for this kind of testing.

Part two: the outbound half, where it got interesting

Now the actual request. The agent needs to call an MCP server on a VM in a separate VNet, and I need to prove the call arrives from a private address.

The mechanism is network injection, officially "Standard Setup with Bring Your Own Virtual Network". You give Foundry a delegated subnet in your VNet, and the agent runtime gets network interfaces in it, so its outbound calls originate from your address space.

Three requirements caught me out before I wrote a single line.

The delegation is Microsoft.App/environments. Not Microsoft.CognitiveServices, which is what I assumed. Agent compute runs on Azure Container Apps under the hood, which is why the delegation belongs to the Microsoft.App namespace. You also have to register both Microsoft.App and Microsoft.ContainerService as resource providers. Skip that and ARM happily accepts your account, then puts it in provisioningState: Failed several minutes later.

Network injection is creation time only. You cannot add it to an existing Foundry account. I had a perfectly good account from part one and it was useless for this. Plan for a rebuild, or better, make the decision before you deploy anything.

Private networking forces Standard Setup, which means bring your own Storage, Cosmos DB and AI Search. These are not optional extras you can add later for convenience. The capability host will not provision without all three. That is real money, AI Search Basic alone is around 75 dollars a month, so budget for it and remember to tear it down.

For the proof I wrote a deliberately dumb mock MCP server. About sixty lines of Node.js, JSON-RPC on port 8080, no authentication, running as a systemd unit. Its only real job is forensic: log req.socket.remoteAddress for every single caller to a file.

const ip = (req.socket.remoteAddress || '').replace(/^::ffff:/, '');
log(`CALLER=${ip}  ${req.method} ${req.url}  ua="${req.headers['user-agent']}"`);

That log file is the entire experiment. Everything else is scaffolding.

MCP server used to emulate Private Link outbound traffic.

The thing that cost me the afternoon

I built everything. Subnet delegated correctly, verified. Providers registered, verified. Account created with networkInjections.scenario = "agent", and I could read the property straight back off the resource to confirm it. Peering connected in both directions. The MCP server responding happily to curl from a VM in the other VNet, logging the caller as 10.0.2.4, so cross-VNet private routing was definitely working.

Then I ran the agent and got this:

BadRequestError: Error code: 400 - {'error': {'message': 'Server returned 424: None',
'type': 'external_connector_error', 'param': 'tools', 'code': 'http_error'}}

And the MCP server log showed nothing at all. Not a failed connection, not a rejected handshake. Zero packets.

That "424" tells you almost nothing. My first instinct was networking, because that is what the error smells like, and because I had just spent an hour on networking. I checked the NSG. I went back over the peering. I confirmed the delegation again. All fine.

The actual answer is that there are two capability hosts, and I had only one.

The account level capability host is created implicitly for you when you set networkInjections. You will see it exist, it will say Succeeded, and it looks like the job is done.

The project level capability host has to be created explicitly, along with connections to your three BYO resources. Until that exists, the agent runtime has nowhere to run. Every tool call fails with that opaque 424, and because the runtime never starts, your tool endpoint never sees a packet, which sends you looking at the network instead of at the thing that is actually missing.

I also had to grant the project's managed identity data plane roles on Storage, Cosmos and Search, or the capability host provisioning itself fails.

Worth noting: the documentation says to expect 30 to 35 minutes for this on a network injected account. Mine completed in about four. Do not cancel it either way, but do not block your afternoon on the higher number.

Account vs Project level - Microsoft Foundry capabilities host.

The proof

With the project capability host in place, I ran it again. The agent's response:

The MCP server reports that your request arrived from source IP 10.0.4.119.

And from the MCP server's own access log:

2026-08-17T15:07:02.378Z  CALLER=10.0.4.119  POST /  ua="AzureAIFoundryAgentRuntime/231188750"
   {"method":"initialize","params":{"protocolVersion":"2025-11-25",...}}
2026-08-17T15:07:02.508Z  CALLER=10.0.4.119  POST /  ua="AzureAIFoundryAgentRuntime/231188750"
   {"method":"tools/list","params":{},"id":2,"jsonrpc":"2.0"}
2026-08-17T15:07:03.829Z  CALLER=10.0.4.119  POST /  ua="AzureAIFoundryAgentRuntime/231188750"
   {"method":"tools/call","params":{"name":"whoami","arguments":{}},"id":2,"jsonrpc":"2.0"}

10.0.4.119 is an address from the delegated agent subnet, 10.0.4.0/24. The user agent is the Foundry agent runtime identifying itself. The full MCP handshake is there, initialize, then tools/list, then tools/call.

That is the whole point of the exercise, in one line of a log file. The agent reached a server in a different virtual network, over peering, from a private IP address, and I can show you the packet arriving rather than asking you to trust an architecture diagram.

iTerm showing the entire TEST suite.

Smaller things that wasted my time

In case you hit the same walls:

  • az vm create would not resolve my subnet. By name it tried to create a new one at 10.0.0.0/24, which was not what I asked for at all, and by full resource ID it insisted the subnet did not exist. I gave up and created the NIC explicitly with az network nic create, then attached it with --nics. Worked immediately.
  • az cosmosdb create has no -l/--location. It wants --locations regionName=... failoverPriority=0.
  • az vm run-command executes under /bin/sh, not bash. My script started with set -euo pipefail and died on line one with "Illegal option -o pipefail".
  • azure-ai-projects 2.x changed shape. list_agents is gone. I used client.get_openai_client() and the Responses API with a native mcp tool type instead, which worked cleanly. It also needs httpx installed and does not pull it in.
  • Role names differ per tenant. "Azure AI User" did not exist in mine. "Azure AI Developer" and "Azure AI Administrator" did.
  • And an early self inflicted one, I put set -euo pipefail in a variables file that was meant to be sourced. That applies the flags to your interactive shell, so the first command returning non-zero closes your terminal window. If a file is designed to be sourced, leave strict mode out of it.

If your DNS lives on premises

This came up straight after, so it is worth including.

Azure Private DNS zones are not reachable from on premises. 168.63.129.16 is a VNet internal address and is not routable over VPN or ExpressRoute, so your own DNS server cannot query the zone directly.

Deploy an Azure DNS Private Resolver with an inbound endpoint in the VNet, then conditional forward from the on premises resolver to that private IP. Forward only the privatelink.* zones. Do not forward openai.azure.com wholesale, because the public lookup returns the CNAME that the whole chain depends on, and taking that over breaks other Azure endpoints.

What I would tell the next person

  • Test both directions, and test the negative case. A 200 from inside proves nothing on its own.
  • Decide on network injection before you create the account, because you cannot add it later.
  • If you get a 424 external_connector_error and your endpoint logs are empty, stop looking at the network. Check whether the project level capability host exists.
  • Budget for Cosmos and AI Search from the start, they are mandatory, not optional.
  • Prove it with a log from the destination. A source IP in an access log is evidence. A diagram is a claim.

Everything is on GitHub, including the raw run logs, so you can see the failures as well as the working version: github.com/raffaeu/azure-foundry-private-link

If you have done this in a hub and spoke topology with a firewall in the path, I would genuinely like to hear how the UDRs worked out for you, because that is the next thing on my list.

Read the whole story
alvinashcraft
just a second ago
reply
Pennsylvania, USA
Share this story
Delete

Choosing a real-time voice architecture on Microsoft Foundry: three enterprise patterns

1 Share

A real-time voice agent is not just a model connected to a microphone. It must detect turns, stream audio, ground answers, call tools, enforce user permissions, recover from connection failures, and keep data on approved network paths. The first architecture question is therefore not which model should speak? but who handles the conversation flow, and how audio reaches the model.

This article compares three practical patterns for real-time voice on Microsoft Foundry, presented as layers added to the same foundation:

  1. Realtime API direct — your application connects straight to a realtime model deployment and owns the conversation runtime.
  2. Voice Live + BYOMVoice Live adds the speech layer in front of a model deployment you control, while your application still handles retrieval and policy.
  3. Voice Live + prompt agent — a Foundry prompt agent adds the orchestration layer, so Microsoft services handle most of the conversation flow.

Here, BYOM (Bring your own model) means a model deployment in your Foundry environment. It does not mean importing model weights or self-hosting the model.

The comparison uses six criteria:

Want the short version? Skip the details and jump to the decision guide.

Start with requirements that cannot change, such as residency, network isolation, or per-user authorization. Compare effort and cost only after each remaining pattern satisfies those requirements.

All three patterns were built against one workload: a bid manager's assistant answering questions about a synthetic tender pack (RFP-2026-014, a main document plus three annexes) in France Central. The prompt-agent and BYOM builds drive a browser client; the direct build is a headless probe, so it does not carry the client-side audio work the other two do. Every number below comes from those builds, measured in August 2026; product behavior changes, so verify anything load-bearing for your own design.

Two decisions, at a glance

It helps to answer two questions separately:

  1. Who handles the conversation flow? This includes turn detection, interruption, session recovery, retrieval, and tool execution.
  2. How does audio reach the model? It can follow a cascade—speech-to-text (STT), then a chat model, then text-to-speech (TTS)—or use a speech-native realtime model.

Two of the three patterns use Voice Live, so it helps to know what it covers. Voice Live handles turn detection, noise suppression, echo cancellation, speech recognition, Azure Speech voices, and playback coordination. These capabilities are what make voice feel like a conversation instead of a walkie-talkie. With a prompt agent, Agent Service additionally handles agent instructions, retrieval, tools, threads, tracing, and approvals.

DimensionRealtime API directVoice Live + BYOMVoice Live + prompt agent
Voice session handled byYour application, using Realtime API events and optional server-side turn detectionVoice LiveVoice Live
Model pathSpeech-nativeSpeech-native or cascaded, selected by the BYOM profileCascaded: STT → chat model → TTS
Model choiceRealtime deployment in your Foundry environmentRealtime, chat, or partner deployment in your Foundry environmentChat deployment selected on the agent
Voice experienceModel-native voicesAzure Speech neural or custom voicesAzure Speech neural or custom voices
RetrievalRAG implemented in your applicationRAG implemented in your applicationManaged File Search / AI Search, or a user-aware tool, with an application edge for user admission and policy
Tool executionBackend functions / MCP clientBackend functions or Voice Live native Model Context Protocol (MCP)Agent Service through project connections
Primary advantageTightest credential and network boundaryVoice Live speech features with control of model, SKU, and throughputSmallest application and orchestration surface
Primary constraintYour application is responsible for the production voice runtimeVoice Live remains a service boundary; native MCP also creates a credential boundaryNo speech-native model path for a prompt agent

These two decisions are related, but they are not the same. Voice Live + BYOM can use a speech-native model even though Voice Live handles the speech session. A prompt agent uses a chat model, so its path is always STT → chat model → TTS.

The patterns form a spectrum. The application handles more responsibilities on the left; Microsoft services handle more on the right.

Responsibility spectrum from a direct Realtime API integration on the left, through Voice Live with your model deployment, to Voice Live with a Foundry prompt agent on the right

Each step to the right hands another responsibility to a Microsoft service; each step to the left keeps it in your application.

The practical question is:

How much of the conversation runtime should the application own, and does this workload need speech-native response time?


Pattern 1 — Realtime API direct

Your backend connects directly to a realtime deployment. The API can provide model events and server-side turn-detection modes. Your application is responsible for configuring and integrating audio buffering, interruption, playback, reconnection, quotas, policy, and the complete tool loop.

Microsoft Foundry Realtime deployment details showing DataZoneStandard, quota, model version, lifecycle, and disabled API keys

The deployment view exposes type, quota, version, and lifecycle. Where Foundry offers a playground for a model, it validates model turns only; it does not exercise the buffering, interruption, authorization, or reconnect logic implemented by your application.

Authenticated client, application-responsible realtime voice runtime, private endpoint, realtime deployment, private RAG, and private tools

Every boundary in this pattern belongs to your application: client ingress, retrieval, tools, and the model connection.

Choose this pattern when private model access, credential custody, private tool reachability, or custom runtime behavior justifies taking responsibility for the voice runtime. Retrieval and MCP are ordinary backend calls, and credentials remain in your process.

Reconsider it when the team does not want to be responsible for audio quality, interruption behavior, connection recovery, capacity protection, and production failover. Azure Speech neural and custom voices are not available on this path, and it carries the largest engineering and operational surface. The shortest model path does not by itself make a complete voice experience.


Pattern 2 — Voice Live + BYOM

Voice Live handles the speech experience and routes inference to a deployment in your Foundry environment. Your application remains responsible for browser trust, private data, user policy, and backend-executed tools.

Voice Live playground showing model, instructions, speech input, voice, and tool settings

A built-in playground sample is shown here, not the RFP assistant from the companion demo. The playground is useful for tuning speech behavior, voices, and tools with a Voice Live managed model; BYOM routing is selected in the connection configuration, not in this view.

Authenticated client, backend, Voice Live, selected BYOM deployment, private RAG, and MCP credential boundary

Voice Live sits between the client and your model deployment, so the speech session and any native MCP call cross a managed service boundary.

The BYOM connection profile determines whether Voice Live sends the turn to a speech-native realtime deployment or uses a cascade through a chat or partner model. In both cases, the model deployment is in your Foundry environment.

BYOM is three integration modes, not one

BYOM profileExample model classAudio pathUse it when
byom-azure-openai-realtimeGPT RealtimeSpeech-nativeYou need realtime latency with Voice Live speech features
byom-azure-openai-chat-completionGPT-5 or GrokCascadedYou need a chat or partner model behind Voice Live
byom-foundry-anthropic-messages (preview)Claude Sonnet / HaikuCascadedYou need an Anthropic deployment in Foundry

See the Voice Live BYOM documentation for current profile, model, and region support. BYOM is useful beyond residency: it can support fine-tuned models, models not predeployed by Voice Live, provisioned throughput (PTU), and deployment-specific content-safety configuration.

Choose this pattern when you want Voice Live to handle speech behavior and Azure Speech voices, while your team controls the model deployment, SKU, capacity, and content filters.

Reconsider it when policy requires every credential and tool call to remain in your process, or when the only accepted model route is a backend-controlled private endpoint and the exact Voice Live-to-deployment route cannot satisfy that requirement. Voice Live remains in the service path, and native MCP requires a service-reachable endpoint that receives caller-provided authorization across that boundary.


Pattern 3 — Voice Live + prompt agent

Voice Live handles the speech experience. Foundry Agent Service handles instructions, model selection, retrieval, and the tool loop.

Scope: This comparison covers Voice Live with a Foundry prompt agent. Foundry Hosted agents run custom agent code and represent a separate architecture that is not evaluated here. Voice Live can also connect to Hosted agents through the Responses or Invocations protocols; that path has a separate runtime, latency, cost, and operating model.

Microsoft Foundry prompt agent playground showing GPT-5, Voice mode, File Search, and Microsoft Learn MCP

A Foundry prompt agent combines its chat deployment, instructions, File Search, MCP tools, and Voice mode in one configuration. The deployment shown is Global Standard, which is convenient for a demo but is exactly the choice that gate 1 tells you to revisit when residency is a requirement.

Voice Live connected to a Microsoft Foundry prompt agent, chat deployment, grounding, and enterprise tools

Voice Live and Agent Service cover the speech session, grounding, and the tool loop; your application keeps the user-facing edge and its policy.

The model attached to a prompt agent is a chat deployment. A realtime deployment cannot back this agent type: in testing, the model and profile parameters were accepted and then silently discarded, and no realtime or audio model appeared anywhere in the session. The audio path is therefore:

microphone → Voice Live STT → chat deployment → Voice Live TTS → speaker

Choose this pattern when managed File Search, threads, traces, MCP discovery, approval events, and automatic tool-loop continuation remove meaningful application work. Interim responses such as “let me check that” can also improve perceived latency while a tool runs.

Reconsider it when speech-native first-response latency is a hard requirement, the managed retrieval path cannot enforce caller-specific document access, or the required network path cannot be demonstrated in the target region and setup. Network isolation is an account-level decision that must be made when the Foundry account is created, and each tool attached to the agent has its own traffic path to validate.


Enterprise production requirements — four release gates

The comparison so far covered features, model path, and implementation effort. The four sections below cover the enterprise production requirements that decide whether a pattern can ship at all: where each turn is processed, which connections can stay private, who is authorized to see grounded content, and which credentials cross a service boundary.

Design against these as release gates, not preferences. A managed feature is not a substitute for evidence that the complete processing and network path satisfies policy. A pattern that fails one gate is eliminated from the shortlist, regardless of how much implementation effort it saves.

These four gates expand criteria two through four from the list at the start. Authorization becomes two gates because grounded content and tool actions fail in different ways: one returns text the caller should not see, the other performs an action the caller should not be able to trigger.


Enterprise gate 1 — Data residency

Residency starts with two checks:

  1. Is Voice Live available in the target region? Start with the official Voice Live region list.
  2. Where can the model deployment process the turn? That answer comes from the model's deployment type, not only from the resource region.
PatternModel inference followsRelease gate
Realtime API directYour realtime deploymentSelect the required deployment type and validate the backend-to-model route
Voice Live + BYOMYour selected model deploymentUse DataZoneStandard or a Regional deployment when required and supported for the model, SKU, profile, and region
Voice Live + prompt agentThe agent's chat deploymentSelect a Data Zone or Regional chat deployment where available, then validate Voice Live speech processing separately

Voice Live's own managed models are not uniformly data-zone, and the mapping differs by region. In France Central at the time of testing, the managed gpt-realtime family resolved to Global Standard, while managed gpt-4o and most gpt-5 variants resolved to Data Zone Standard. Do not assume that a managed model inherits the residency of your resource: check the deployment type for the exact model and region, or use BYOM so the deployment type is one you choose.

Speech processing, retrieval, storage, tools, transcripts, telemetry, and logs are separate processing surfaces. Record the region and deployment type for each one. A compliant model deployment does not make the whole voice system compliant.


Enterprise gate 2 — Private networking

A private data source does not make the whole voice path private. Review each connection: client to application, application to speech service, application to model, application to retrieval, service or backend to tools, and every telemetry path.

Optional authenticated public edge with a private backend, private RAG and tools, private endpoints, and managed Foundry service paths

A public, authenticated edge can front a fully private data plane—but every managed service link behind it still needs its own evidence.

PatternApplication-controlled or private pathsManaged path that still needs evidenceRelease gate
Realtime API directBackend-to-model, retrieval, and tools through private endpoints where supportedClient ingress and any telemetry or external dependency you chooseValidate private DNS, disabled public model access, explicit egress, reconnect behavior, and operational access
Voice Live + BYOMBackend, retrieval, backend functions, and private application dependenciesClient/backend-to-Voice Live, Voice Live-to-deployment routing, and any native MCP callVerify the exact BYOM profile, region, and route before assuming a deployment with public access disabled is reachable
Voice Live + prompt agentBackend, private data, Search, Storage, Cosmos DB, and supported project connectionsVoice Live-to-Agent Service orchestration and every Agent Service dependencySelect the isolated topology (bring-your-own or managed virtual network) when the Foundry account is created, build its delegated subnet and private endpoints, then test name resolution, traffic, and each attached tool from the deployed environment

Four rules prevent overclaiming:

  • An authenticated, rate-limited public application edge can be acceptable when it exposes no private data, credentials, or tool plane. “Public ingress” and “public data plane” are not the same statement.
  • A private endpoint on one resource does not prove that every managed service link uses that endpoint.
  • Tool connectivity is part of the network decision. In a Foundry Standard project with a bring-your-own virtual network, your data and tool resources, Azure AI Search included, are reached through their private endpoints. A toolbox groups several tools but deploys no networking of its own, so each tool inherits the project configuration and follows its own path. See network isolation for a toolbox for the per-tool detail.
  • Voice Live is built on Azure Speech, but support must be confirmed for the exact Voice Live feature and route. Use the Speech Private Link documentation as a starting point, not as proof of the complete architecture.

Enterprise gate 3 — Per-user authorized retrieval

Voice RAG has a dangerous failure mode: retrieval can silently return no authorized results, after which the model answers fluently and incorrectly, out loud, without a result list the user can inspect.

The architecture must answer who queries, with whose identity, and where document entitlements are enforced.

Authenticated RAG flow from signed-in user and document entitlements through the retrieval executor to private knowledge and authorized model context

The workload identity opens the data source; the caller's entitlements decide which text may reach the model.

PatternWho queriesRetrieval credentialPer-user filtering
Realtime API directYour backendBackend managed identity, scoped to Search Index Data ReaderApply security trimming before returning text to the model
Voice Live + BYOMYour backendBackend managed identity, scoped to Search Index Data ReaderApply security trimming before returning text to the model
Voice Live + prompt agentFoundry Agent ServiceProject connection using managed identity or API keyManaged File Search is workload-scoped; use a user-aware tool or MCP connection when results must vary by caller

A workload identity that may query an index does not mean every caller may see every document. Managed File Search is a good fit when every authorized user may search the same corpus. If document access varies by caller, route retrieval through a custom function or MCP tool. The backend keeps the index credential, evaluates the caller's claims, and returns only authorized text to the model.

Treat “no authorized results” as an explicit outcome. Log it with the session correlation ID and either tell the user that no accessible source was found or follow a documented ungrounded-answer policy. Do not silently broaden the query or fall back to a less restricted corpus.

Network isolation must cover ingestion as well as query traffic. In a private Agent Service setup, verify that Search indexers execute through the private environment; otherwise deployment can appear healthy while the index remains empty.


Enterprise gate 4 — Tool credentials and action authorization

Voice Live supports function calling and native MCP. The difference is not syntax—it is execution and credential custody.

Comparison of a backend function path that keeps credentials in the application and a native MCP path that crosses a managed service boundary

Use the execution path to make credential custody and network reachability explicit.

AspectFunction callingNative MCP in Voice Live
Tool executionApplication-side, normally your backendServer-side, managed by Voice Live
Tool discoveryYou declare schemasVoice Live discovers tools from the MCP endpoint
ApprovalYou build the policy and user experiencealways (default), never, or per-tool
Network reachabilityWhatever your backend can reachMCP endpoint must be reachable by Voice Live
CredentialRemains in your backendCaller-provided authorization or headers are passed to Voice Live
API version2025-10-012026-04-10 or later
  • With Realtime API direct, your backend is the MCP client and can use managed identity, OAuth/OBO, or a Key Vault secret while reaching private tools.
  • With Voice Live + BYOM, Voice Live is the client for native MCP and receives the authorization material. Backend functions instead keep credentials in your process.
  • With a Voice Live + prompt agent setup, Agent Service is the MCP client; project connections can use managed identity, agentic identity, OAuth, or a user token for on-behalf-of (OBO) authorization.

Treat the tool allow-list as a security boundary: without one, tools added to the server later can become callable. Automatic execution may be appropriate for low-risk, read-only operations, but read-only does not automatically mean safe. Writes, spending, privilege changes, and sensitive reads need application-side authorization and an approval experience; high-impact actions may also require step-up authentication.

Approval configuration alone is not the user experience. Design how the conversation states the pending action, waits, handles denial, and avoids replaying a side effect after reconnect. A prompt agent manages the tool-to-response loop; with native MCP or backend functions, verify which component continues the turn and requests the spoken answer.


Cost — Count the meters before the tokens

Start with the billing topology. Each managed layer can add a meter, while moving responsibility into your application adds engineering and operating cost that token counts do not show:

PatternCost topologyPrimary cost controls
Realtime API directYour model deployment + application, data, and tool infrastructureModel usage, capacity, application runtime, operational effort
Voice Live + BYOMVoice Live + your model deployment + backend retrieval and toolsModel and SKU, provisioned throughput, session duration, tool volume
Voice Live + prompt agentVoice Live + chat deployment + retrieval and tool services as usedChat model, retrieved context, session duration, tool volume

Voice Live itself does not bill at a single rate. Its meter is tiered by model: pro covers models such as gpt-realtime, gpt-4o, and gpt-5; basic covers the mini variants; lite covers gpt-5-nano and Phi. Confirm which tier applies to a BYOM session before sizing, because the published tier table enumerates the managed models rather than bring-your-own deployments.

A configuration measurement, not a pattern ranking

The following numbers came from one identical question and target answer in the tested configurations. They are intentionally retained as billing anatomy, not as a normalized price comparison: the prompt-agent path used GPT-5 and managed File Search, while the other paths used GPT Realtime and backend retrieval.

Tested configurationTotal tokensInputOutput textOutput audioReasoning detail*
Realtime API direct1,8511,640501610
Voice Live + BYOM1,8651,639331930
Voice Live + prompt agent (GPT-5 + File Search)7,2076,324668215576

* Reasoning is reported as detail within the output text tokens, not as an additional category to add to the total.

Do not divide the last row by the others and call the result a pattern multiplier. That would confound model choice, reasoning, retrieval implementation, context size, and response shape. The runs support narrower conclusions:

  • Direct and BYOM reported similar usage because they used the same model for the same work, but they still cross different billing topologies.
  • Direct has one model-inference meter but moves voice-runtime cost into application engineering and operations; BYOM adds the Voice Live tier to the deployment in your Foundry environment.
  • Anthropic BYOM reports model and audio usage separately. Reconcile both in Cost Management before sizing.
  • File Search chunks dominated the tested prompt-agent input; reasoning dominated its reported output.

If commercial requirements favor predictable throughput over per-token billing, PTU on a deployment in your Foundry environment points toward Voice Live + BYOM or Realtime API direct.


Latency — A directional test, not a benchmark

In this test, changing the chat model inside one pattern moved p50 by about 2.7 seconds, while the two speech-native patterns differed by 80 milliseconds. The pattern set the floor; the model chosen inside it decided most of the rest.

The test was run on 15 August 2026 using ten warm, text-injected, no-tool turns per configuration on one workstation in France Central. The prompt-agent path was measured with two chat models to show how much model choice can change the result within the same pattern. Realtime model versions turn over quickly; check current availability before reusing these numbers.

Warm p50 first-audio latency comparing the direct Realtime API and Voice Live BYOM with Voice Live prompt agents on GPT-4o mini and GPT-5

The two prompt-agent bars use the same pattern and the same speech layer; only the chat model behind them differs.

Tested patternModelWarm p50, n=10
Realtime API directGPT Realtime 1.5, speech-native0.34 s
Voice Live + BYOMGPT Realtime 1.5, speech-native0.42 s
Voice Live + prompt agentGPT-4o mini, chat, non-reasoning1.70 s
Voice Live + prompt agentGPT-5, chat, reasoning4.37 s

The probe captured p95 values, but ten samples are not enough to present p95 as a reliable tail-latency statistic. The p50 results support only directional observations:

  • The fast prompt-agent configuration remained about 1.3 seconds behind BYOM. That gap includes the serial chat-completion and TTS path, plus service, model, and voice differences that this test did not isolate.
  • BYOM and direct were close in this environment. Their 80 ms difference is not a standalone measurement of Voice Live overhead.
  • In an earlier exploratory run, retrieval raised the fast prompt-agent path to roughly 4.7 seconds p50, and session establishment added roughly 4.5 seconds. Tool latency and cold connection time need separate budgets.

A critical limitation is asymmetric: text injection excluded STT from every track, but only the cascaded prompt-agent path has a real STT hop. The true spoken-input gap is therefore likely larger than the measured 1.3 seconds.

A real customer benchmark should use real microphone audio, the actual client stack, production retrieval and tools, target region and SKU, realistic concurrency, hundreds of interleaved turns, and p95/p99. Measure what users feel: user stopped speaking → agent started speaking.

Also measure perceived latency. The tested prompt-agent integration can provide a managed interim “let me check that” response while a tool runs. A direct integration can play application-generated status audio, but your runtime must coordinate it with interruption, tool completion, and the final model response. Four seconds with feedback feels different from four seconds of silence.


Decision guide — Let the binding constraint choose

Use the same six criteria from the start of the article. Treat a requirement as a hard gate when the architecture cannot compensate for it elsewhere.

CriterionRealtime API directVoice Live + BYOMVoice Live + prompt agent
Features and effort to buildRealtime model and protocol; your application handles the complete conversation runtimeVoice Live handles speech; your application handles retrieval, policy, and part of the tool loopVoice, retrieval, tools, threads, tracing, and approvals are handled by Microsoft services
Data residencyYour deployment type controls model residencyYour deployment can use Data Zone or Regional SKUs while retaining Voice LiveInference follows the agent's chat deployment type
Private networkingClearest private model and tool boundary, with the most application implementationPrivate application and data plane; prove the exact Voice Live-to-deployment and native MCP routesMost service-side networking configuration; validate Voice Live, Agent Service, and every dependency
Authentication and authorizationYour backend is responsible for every credential, entitlement check, and tool callBackend functions keep per-user policy private; native MCP crosses a service boundaryProject connections provide managed identity, OAuth, and OBO patterns
CostFewest managed meters, but engineering and operating effort becomes the dominant costPredictable model spend through your own SKU and capacity, plus the Voice Live tierMost managed meters, and retrieved context drives model spend
LatencyShortest model path; end-to-end latency depends on the voice runtime you buildSpeech-native latency with Voice Live speech featuresCascaded path; model choice matters, but speech-native latency is not available

Apply the guide in three steps

  1. Mark the non-negotiable requirements. Examples include a Data Zone deployment, Azure Speech custom voices, private tool reachability, or a speech-native latency target.
  2. Eliminate any pattern that fails a hard gate. Do not use additional features to compensate for a residency, authorization, or network requirement.
  3. Compare effort and cost only among the remaining patterns. Then benchmark the finalists with real audio, retrieval, tools, concurrency, and the target region.

If no hard gate separates the finalists, use ownership as the tie-breaker: Voice Live + BYOM balances managed speech with deployment control; Realtime API direct maximizes runtime and credential control; and a prompt agent maximizes managed orchestration.

There is still no universal winner. The best pattern is the least complex one that satisfies every hard gate—and whose unverified service paths you can close before release.


Get started

Start with the companion demo to exercise all three paths against the same use case, then use the product documentation to go deeper on the pattern you select.

PathNext step
Run and compare all three patternsCompanion demo repository with setup instructions, the synthetic RFP corpus, and latency/cost probes
Try the direct model pathRealtime API over WebSockets
Try Voice LiveVoice Live quickstart
Prepare security reviewDeployment types · Speech regions · Agent Service networking · Toolbox network isolation · Speech Private Link
Understand toolsMCP with Voice Live · Foundry Agent Service MCP

The companion repository is a local reference implementation, not production infrastructure: it uses developer credentials and does not deploy user authentication or private networking.

Which of the six criteria becomes the binding constraint in your environment? Share it in the comments.

Read the whole story
alvinashcraft
11 seconds ago
reply
Pennsylvania, USA
Share this story
Delete

Migrate a .bak to Azure SQL Database primary‑key violation (SQL Error 2627)

1 Share

Problem Statement

Customer trying to mograte SQL Server database to Azure SQL Database by restoring a .bak file to a jump server, exporting it to a .bacpac, and importing that .bacpac to Azure SQL with SQL Server Management Studio (SSMS). The import failed with a primary‑key violation (SQL Error 2627), even though the source database had no duplicate keys.

SQL Error 2627 Violation of PRIMARY KEY constraint 'pk_table_parameters'. Cannot insert duplicate key in object 'dbo.table_parameters'.

The cause was a collation mismatch. The source used a case‑sensitive collation, so key values that differed only by letter case were unique. When SSMS created the target database, it used the default case‑insensitive collation, so those values became duplicates and the primary key rejected them.

The fix is to pre‑create the Azure SQL database with the source's case‑sensitive collation, then import the .bacpac into that existing empty database using the SqlPackage command‑line utility (run from an Azure VM for large files or restricted networks).

Environment

AttributeValue
SourceSQL Server database restored from a .bak file
Migration path.bak → .bacpac (SSMS export) → import to Azure SQL Database
Failing toolSSMS import (creates the target with default collation)
ErrorPrimary‑key violation (SQL Error 2627)
TargetAzure SQL Database

 

Root Cause

Collation decides whether string comparisons are case‑sensitive (CS) or case‑insensitive (CI). Under CS, ABC and abc are different values; under CI, they are equal.

  • The source database used a case‑sensitive collation (for example, Latin1_General_100_CS_AS), so its key values were legitimately unique.
  • When SSMS imported the .bacpac, it created a new target database using the default collation, which is case‑insensitive (SQL_Latin1_General_CP1_CI_AS).
  • Under the case‑insensitive target, key values that differed only by case collapsed into duplicates, and the primary key rejected the second row with SQL Error 2627.

On Azure SQL Database, you cannot change the database collation afterward — ALTER DATABASE ... COLLATE isn't supported. The collation must be set when the database is created.

Solution

Set the target collation correctly before loading data, then import into that existing database.

Step 1 — Pre‑create the Azure SQL database with the source collation

Run in the master database on the target logical server. 

CREATE DATABASE [TargetDb] COLLATE Latin1_General_100_CS_AS;

Step 2 — Import the BACPAC into the existing database with SqlPackage

The Azure SQL Database import documentation supports importing a .bacpac into an existing empty database, and recommends SqlPackage for larger or production migrations. Running SqlPackage from an Azure VM avoids local disk and network limits.

SqlPackage import into an EXISTING empty database (preserves its collation) 

SqlPackage /Action:Import ^ /SourceFile:"C:\migrate\TargetDb.bacpac" ^ /TargetServerName:"<server>.database.windows.net" ^ /TargetDatabaseName:"TargetDb" ^ /TargetUser:"<userId>" ^ /TargetPassword:"<password>"

 

Because the database already exists with the correct case‑sensitive collation, SqlPackage loads the data into it and the key values stay unique — no 2627 collisions.

You don't have to pre‑create the database. If you point SqlPackage at a target database name that does not yet exist, SqlPackage creates it and the new database inherits the collation from the BACPAC — which is the source database's case‑sensitive collation. Either approach works; the key point is that the target must not end up with the default case‑insensitive collation. The SSMS import failed precisely because SSMS created the target with the default (case‑insensitive) collation.

Performance tip — pre‑create a database with a higher service objective. A BACPAC exported from a non‑Azure source (an on‑premises or .bak database) contains no service‑level objective (SLO) metadata. When SqlPackage creates the target, it uses a default SLO, which can make a large import run slowly. Pre‑creating the target lets you assign more resources (a higher service tier and compute size) so the import completes faster; you can then scale down after it succeeds. You can also pass the SLO to SqlPackage on create with /p:DatabaseEdition and /p:DatabaseServiceObjective.

Connectivity: To reach Azure SQL Database, port 1433 must be open from the machine running SqlPackage (including an Azure VM). Adjust firewall and network rules to your organization's security policy.

Because the database already exists with the correct case‑sensitive collation, SqlPackage loads the data into it and the key values stay unique — no 2627 collisions.

Connectivity: To reach Azure SQL Database, port 1433 must be open from the machine running SqlPackage (including an Azure VM). Adjust firewall and network rules to your organization's security policy.

Step 3 — Verify the collation

Run against the target database. 

SELECT DATABASEPROPERTYEX(DB_NAME(), 'Collation') AS DatabaseCollation; -- Expected: Latin1_General_100_CS_AS

 Expected: Latin1_General_100_CS_AS

Validation

  • Confirm the target collation matches the source (Step 3).
  • Compare row counts between source and target.
  • Start the application and spot‑check data.

Key Takeaways

  1. SSMS import creates the target database with the default (case‑insensitive) collation. For a case‑sensitive source, that alone can cause SQL Error 2627.
  2. Two fixes work: let SqlPackage create a new database (it inherits the source collation from the BACPAC), or pre‑create the database with the correct collation and import into it.
  3. You can't change collation later on Azure SQL Database. Set it at CREATE DATABASE, or let the BACPAC define it.
  4. Use SqlPackage (optionally from an Azure VM) for larger or restricted migrations. Keep port 1433 open.
  5. Pre‑creating the target lets you assign a higher service objective for a faster import. A non‑Azure BACPAC has no SLO metadata, so a SqlPackage‑created database defaults to a low SLO; use a higher tier during import and scale down after.
  6. Match the source collation before migrating whenever the source is case‑sensitive.

References

 

 

Read the whole story
alvinashcraft
16 seconds ago
reply
Pennsylvania, USA
Share this story
Delete

What's new in Swift: August 2026 Edition

1 Share

Welcome to “What’s new in Swift,” a curated digest of releases, videos, and discussions in the Swift project and community.

Here’s an update from guest contributor Simon Leeb on Swift’s progress as a language for web scenarios:

Hi, Simon here! I am the creator of the elementary-swift project, a collection of packages born from a simple wish: I want to build web UIs in Swift and ultimately help Swift become a first-class choice for the web.

This journey began after I started using Swift for backend services. The web frontend, however, still lived in a separate ecosystem, and I really wanted it to feel as ergonomic, safe, and efficient as the Swift I was writing everywhere else.

That led to the creation of Elementary: a modern and efficient HTML rendering library with a familiar declarative API, built for the web. It integrates easily with frameworks like Vapor and Hummingbird, and has become a practical option for server-rendered web UIs.

Around that same time, years of community work in the swift-wasm project made compiling Swift to WebAssembly increasingly viable, while Embedded Swift was taking its first experimental steps. This made me wonder: “How hard can it be to use Embedded Swift and build a state-driven web UI framework that produces tiny WebAssembly binaries?” Turns out: quite hard, actually!

But it was too late. Despite my better judgment, I was in the middle of creating what is now known as ElementaryUI. Where Elementary renders HTML on the server, ElementaryUI runs in the browser itself. You can watch my talk at Swift@FOSDEM 2026 if you want to know more about the why, what, and how.

To showcase where the project is heading, I recently posted a small Full-Stack Swift on Cloudflare demo. It features Swift in the browser communicating with a Swift backend on an edge worker through shared message types. I hope it gives people a concrete sense of how much the core technologies and the surrounding tooling have advanced.

ElementaryUI is still young, with plenty left to build. Visit elementary.codes to try it, share feedback, contribute, or sponsor its development. Let’s work together and make Swift a first-class choice for the web!

Now on to other news about Swift:

Videos to watch

  • Saleem Abdulrasool joined the Empower Apps podcast to discuss Swift on Windows, server-side Swift, SwiftWin32, Swift’s C++ interop, and how to get started.
  • Building memory-safe software? Write security-sensitive code in Swift covers how Swift guarantees safety across bounds, lifetimes, types, initialization, and concurrency, with primitives like Span and non-copyable types, plus how to audit unsafe code with strict memory safety and incrementally migrate existing C modules.
  • Two short videos about running Embedded Swift on Raspberry Pi Pico: a video on getting started on macOS called Let it blink! and Fun with traffic lights in 60 seconds.

Community highlights

New package releases

  • Write an interface once with SwiftTUI using a declarative, state-driven syntax, then ship it as a terminal app, as a native macOS or iOS app, as an Android app, or as a WASI build for the browser.
  • Tired of hand-writing RawRepresentable and LosslessStringConvertible conformances? lexic generates them for you via macros, and runs the same on Linux as on Apple platforms.
  • StructuredQueries, Point-Free’s SQLite query builder, now has fully type-safe support for JSON and JSONB columns, including a json_each table function, so nested data can be queried and updated by key path without leaving Swift’s type system.

Swift Evolution

The Swift project adds new language features through the Swift Evolution process. These are some of the proposals currently under review or recently accepted for a future Swift release.

Under active review:

  • ST-0029 Include additional issue metadata in event stream - Today, Swift Testing’s JSON event stream reports only bare-bones details when an issue occurs, making it hard to tell a thrown error apart from a manual Issue.record call. This proposal adds structured fields, including error, confirmationMiscount, exceededTimeLimit, and expression, so tools like Xcode and VS Code can show richer, more specific failure information.

Recently accepted:

  • SE-0544 Mutation and consumption in non-copyable type deinits - Non-copyable types that manage a resource, like a file handle or buffer, often need to run the same cleanup logic in their deinit that they use elsewhere, but until now self inside a deinit could only be borrowed, not mutated or consumed. This proposal lets a deinit mutate or consume its own stored properties directly, so existing cleanup methods can be reused instead of duplicated.
  • ST-0028 Revise Swift Testing’s Attachment/Encodable interop - Swift Testing lets you attach extra data, like a screenshot or JSON snapshot, to a test for inspecting after a failure, but attaching custom types previously required extra setup code and offered no way to choose the encoding format. This proposal adds new Attachment initializers that let you attach Encodable or NSSecureCoding values directly, picking the format or supplying your own encoder.

Recently accepted with modifications:

  • SE-0536 Package Registry Search - To use a package from a registry today, you already have to know its exact identifier, since there’s no standard way to discover packages within a registry the way other package ecosystems allow. This proposal adds an optional /search endpoint to the registry specification and a swift package-registry search subcommand, letting you find packages by name, scope, author, and other criteria, with support for qualifiers like author:"Mona Lisa Octocat" and searches that span every configured registry at once.
  • SE-0516 Iterable - Looping over a collection in Swift traditionally means copying out one element at a time, which doesn’t work for newer types that can’t be copied, like Span and InlineArray. This proposal introduces Iterable, a new way to loop over data without copying, and was renamed from BorrowingSequence and given support for typed throws before acceptance.
  • ST-0026 TaskLocal test trait - Task-local values are like settings, such as a feature flag, that apply only within a single task. Overriding one in a test previously meant writing a custom trait from scratch, but this proposal adds a .taskLocal(_:_:) trait that does it in one line, like @Suite(.taskLocal(FeatureFlags.$isEnabled, true)).
Read the whole story
alvinashcraft
27 seconds ago
reply
Pennsylvania, USA
Share this story
Delete

Azure SDK Release (August 2026)

1 Share

Thank you for your interest in the new Azure SDKs! We release new features, improvements, and bug fixes every month. Subscribe to our Azure SDK Blog RSS Feed to get notified when a new release is available.

You can find links to packages, code, and docs on our Azure SDK Releases page.

To restore a complete release record, this roundup also includes 49 initial stable and beta releases that were publicly available from package registries in July but weren’t listed in the July post because several release-data updates hadn’t merged. Public package availability, rather than release-data pull request status, determines inclusion.

Release highlights

Document Translation 2.0.0

Document Translation added support for the 2026-03-01 service API, including translation of text embedded in images for batch and single-document requests, custom translation model deployments, and expanded image scan reporting. The Java and Python 2.0.0 releases include breaking changes, while the new JavaScript package replaces the REST-level client with modeled DocumentTranslationClient and SingleDocumentTranslationClient APIs.

Storage – Blobs 12.30.0-beta.1

Azure Storage preview releases across .NET, Java, JavaScript, Python, Go, and C++ added support for service version 2026-10-06. Blob libraries introduced Apache Arrow response support for listing operations, additional access-tier metadata, and responses that can include both MD5 and CRC64 hashes. File Share libraries added paged range-list APIs, and .NET Blob uploads now generate random block IDs to prevent collisions during concurrent uploads.

Azure AI Discovery 1.0.0

Azure AI Discovery reached its first stable release for Python and JavaScript. The libraries expose Workspace capabilities for conversations, investigations, tasks, and tools, plus Bookshelf knowledge-base lifecycle, indexing, and citation-aware search operations. The stable Python release also adds paged responses, storage mount protocol controls, and long-running cancellation while establishing a new compatibility baseline from the preview.

Initial stable releases

Initial beta releases

Release notes

The post Azure SDK Release (August 2026) appeared first on Azure SDK Blog.

Read the whole story
alvinashcraft
45 seconds ago
reply
Pennsylvania, USA
Share this story
Delete

W3C opens 2026 community survey

1 Share

Following on the success of the surveys of the W3C community over the last few years, we are conducting the W3C 2026 Survey to get to know our community better, investigate needs, and understand how we can improve.

Further to the 2025 survey, we already implemented changes that W3C Members benefit from, such as quarterly onboarding calls and monthly newsletters, to name only a few.

This year's survey is being run through SurveyMonkey and should take about 8 minutes to complete. It is available to members as well as those who are part of the larger W3C community. Answers are anonymous.

The survey closes on 30 September 2026. We look forward to hearing from you!

Read the whole story
alvinashcraft
1 minute ago
reply
Pennsylvania, USA
Share this story
Delete
Next Page of Stories