Skip to content

Commit ef520a2

Browse files
ferantiverockittelweb-flow
authored
patch (website): [refresh] port ChatUI from basic and use new Foundry (new) API (#87)
Co-authored-by: Chad Kittel <chad.kittel@gmail.com> Co-authored-by: GitHub Copilot (Claude Opus 4.6) <noreply@github.com>
1 parent 9bb040a commit ef520a2

File tree

9 files changed

+333
-134
lines changed

9 files changed

+333
-134
lines changed

README.md

Lines changed: 39 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -225,16 +225,16 @@ The AI agent definition would likely be deployed from your application's pipelin
225225
$FOUNDRY_PROJECT_NAME="projchat"
226226
$MODEL_CONNECTION_NAME="agent-model"
227227
$BING_CONNECTION_ID="$(az cognitiveservices account show -n $FOUNDRY_NAME -g $RESOURCE_GROUP --query 'id' --out tsv)/projects/${FOUNDRY_PROJECT_NAME}/connections/${BING_CONNECTION_NAME}"
228-
$FOUNDRY_AGENT_CREATE_URL="https://${FOUNDRY_NAME}.services.ai.azure.com/api/projects/${FOUNDRY_PROJECT_NAME}/agents?api-version=2025-11-15-preview"
228+
$FOUNDRY_AGENT_URL="https://${FOUNDRY_NAME}.services.ai.azure.com/api/projects/${FOUNDRY_PROJECT_NAME}/agents?api-version=2025-11-15-preview"
229229
230230
echo $BING_CONNECTION_ID
231231
echo $MODEL_CONNECTION_NAME
232-
echo $FOUNDRY_AGENT_CREATE_URL
232+
echo $FOUNDRY_AGENT_URL
233233
```
234234

235-
1. Deploy the agent.
235+
1. Create the agent.
236236

237-
*This step simulates deploying an AI agent through your pipeline from a network-connected build agent.*
237+
*This step provisions a long-lived AI agent from your jump box. This simulates a network-connected build agent performing in your pipeline.*
238238

239239
```powershell
240240
# Use the agent definition on disk
@@ -248,15 +248,47 @@ The AI agent definition would likely be deployed from your application's pipelin
248248
249249
$chat_agent | ConvertTo-Json -Depth 10 | Set-Content .\chat-with-bing-output.json
250250
251-
# Deploy the agent
252-
az rest -u $FOUNDRY_AGENT_CREATE_URL -m "post" --resource "https://ai.azure.com" -b @chat-with-bing-output.json
251+
# Persist the agent
252+
az rest -u $FOUNDRY_AGENT_URL -m "post" --resource "https://ai.azure.com" -b @chat-with-bing-output.json
253253
254254
# Capture the Agent's ID
255-
$AGENT_ID="$(az rest -u $FOUNDRY_AGENT_CREATE_URL -m 'get' --resource 'https://ai.azure.com' --query last_id -o tsv)"
255+
$AGENT_ID="$(az rest -u $FOUNDRY_AGENT_URL -m 'get' --resource 'https://ai.azure.com' --query last_id -o tsv)"
256256
257257
echo $AGENT_ID
258258
```
259259

260+
| :information: | You’ve just persisted a new versioned agent in Foundry AI Agent Service, including its instructions, tools, and model. The platform has stored a canonical agent definition in the `enterprise_memory` database, making the agent addressable, executable and ready for evaluation. At this stage, the agent is available for validation, and has the `unpublished` state. Because this is your first agent, this step is also when the Foundry project provisions a default agent identity blueprint and a default agent identity for your project in Microsoft Entra Agent ID. All `unpublished` agents within the same Foundry project share this default agent identity until they are `published`.|
261+
| :-------: | :------------------------- |
262+
263+
1. Publish the the Agent
264+
265+
*This step publishes the agent by creating a new application within the Foundry project and a corresponding deployment that references a specific agent version.*
266+
267+
```bash
268+
az deployment group create -f ./infra-as-code/bicep/ai-foundry-appdeploy.bicep \
269+
-g $RESOURCE_GROUP \
270+
-n 'foundryAppDeploy' \
271+
-p baseName=${BASE_NAME}
272+
```
273+
274+
| :information: | As a result, the agent becomes a nested Azure resource visible in the Azure control plane. Publishing the chat agent automatically created a dedicated agent identity blueprint and agent identity. Both are bound to the Azure Foundry application resource. This distinct identity represents the chat agent's system authority for accessing its own resources. Reassigning RBAC permissions was required so the new agent identity get permissions to access the conversation, vector store and storage resources. At this deployment time, it was a great moment to reassess only the permissions the agent needs for its tool actions. |
275+
| :-------: | :------------------------- |
276+
277+
1. Verify the agent deployment is running
278+
279+
*This step verify the Foundry AI Agent Service deployment is runnning by invoking the agent application's responses endpoint.*
280+
281+
```powershell
282+
$AGENT_BASE_URL="$(az deployment group show -g $RESOURCE_GROUP -n 'foundryAppDeploy' --query "properties.outputs.agentApplicationBaseUrl.value" -o tsv)"
283+
284+
$AGENT_RESPONSES_URL="${AGENT_BASE_URL}/protocols/openai/responses?api-version=2025-11-15-preview"
285+
286+
az rest -u $AGENT_RESPONSES_URL -m "post" --resource "https://ai.azure.com" -b '{\"input\": \"Say hello\"}' --query "{agent:agent.name, agent_version:agent.version,output:output[-1].content[0].text}"
287+
```
288+
289+
| :information: | The terminal displays the agent application’s response, verifying that the specified agent version is running inside the deployment. |
290+
| :--------: | :------------------------- |
291+
260292
### 3. Test the agent from the Foundry portal in the playground. *Optional.*
261293

262294
| :warning: | The new Foundry portal experience does not currently support the end-to-end network isolation used in this architecture. Using this secured architecture, you will only be able to create and call your agents through the SDK or REST API; not interface with them in the Foundry portal. See, [How to use a virtual network with the Foundry Agent Service](https://learn.microsoft.com/azure/ai-foundry/agents/how-to/virtual-networks?view=foundry&preserve-view=true). These intermediate testing instructions will be updated when this experience is supported. |

agents/chat-with-bing.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "baseline-chatbot-agent",
3-
"description": "Example of a Foundry Agent that uses the Bing Search tool to answer questions. Used in the Microsoft Learn AI chat reference architecture. https://learn.microsoft.com/azure/architecture/architecture/baseline-azure-ai-foundry-chat",
3+
"description": "Example of a Foundry Agent that uses the Bing Search tool to answer questions. Used in the Microsoft Learn AI chat reference architecture. https://learn.microsoft.com/azure/architecture/ai-ml/architecture/baseline-microsoft-foundry-chat",
44
"definition": {
55
"kind": "prompt",
66
"model": "MODEL_CONNECTION_NAME",

infra-as-code/bicep/ai-agent-blob-storage.bicep

Lines changed: 0 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -22,28 +22,13 @@ param logAnalyticsWorkspaceName string
2222
@minLength(1)
2323
param privateEndpointSubnetResourceId string
2424

25-
@description('The existing User Managed Identity for the Foundry project.')
26-
@minLength(1)
27-
param existingAgentUserManagedIdentityName string
28-
2925
// ---- Existing resources ----
3026

31-
@description('Existing Agent User Managed Identity for the Foundry project.')
32-
resource agentUserManagedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2025-01-31-preview' existing = {
33-
name: existingAgentUserManagedIdentityName
34-
}
35-
3627
resource storageBlobDataOwnerRole 'Microsoft.Authorization/roleDefinitions@2022-04-01' existing = {
3728
name: 'b7e6dc6d-f1e8-4753-8033-0f276bb0955b'
3829
scope: subscription()
3930
}
4031

41-
// Storage Blob Data Contributor
42-
resource storageBlobDataContributorRole 'Microsoft.Authorization/roleDefinitions@2022-04-01' existing = {
43-
name: 'ba92f5b4-2d11-453d-a403-e96b0029c9fe'
44-
scope: subscription()
45-
}
46-
4732
resource blobStorageLinkedPrivateDnsZone 'Microsoft.Network/privateDnsZones@2024-06-01' existing = {
4833
name: 'privatelink.blob.${environment().suffixes.storage}'
4934
}
@@ -111,16 +96,6 @@ resource debugUserBlobDataOwnerAssignment 'Microsoft.Authorization/roleAssignmen
11196
}
11297
}
11398

114-
@description('Grant the Foundry project managed identity Storage Account Blob Data Contributor user role permissions.')
115-
module projectBlobDataContributorAssignment './modules/storageAccountRoleAssignment.bicep' = {
116-
name: 'projectBlobDataContributorAssignmentDeploy'
117-
params: {
118-
roleDefinitionId: storageBlobDataContributorRole.id
119-
principalId: agentUserManagedIdentity.properties.principalId
120-
existingStorageAccountName: agentStorageAccount.name
121-
}
122-
}
123-
12499
// Private endpoints
125100

126101
resource storagePrivateEndpoint 'Microsoft.Network/privateEndpoints@2024-05-01' = {

infra-as-code/bicep/ai-agent-service-dependencies.bicep

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,6 @@ param privateEndpointSubnetResourceId string
2424

2525
// ---- New resources ----
2626

27-
@description('The agent User Managed Identity for the Foundry project. This is used when a user uploads a file to the agent, and the agent needs to search for information in that file.')
28-
resource agentUserManagedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2025-01-31-preview' = {
29-
name: 'mi-agent-${baseName}'
30-
location: location
31-
}
32-
3327
@description('Deploy Azure Storage account for the Foundry Agent Service (dependency). This is used for binaries uploaded within threads or as "knowledge" uploaded as part of an agent.')
3428
module deployAgentStorageAccount 'ai-agent-blob-storage.bicep' = {
3529
name: 'agentStorageAccountDeploy'
@@ -40,7 +34,6 @@ module deployAgentStorageAccount 'ai-agent-blob-storage.bicep' = {
4034
logAnalyticsWorkspaceName: logAnalyticsWorkspaceName
4135
debugUserPrincipalId: debugUserPrincipalId
4236
privateEndpointSubnetResourceId: privateEndpointSubnetResourceId
43-
existingAgentUserManagedIdentityName: agentUserManagedIdentity.name
4437
}
4538
}
4639

@@ -54,7 +47,6 @@ module deployCosmosDbThreadStorageAccount 'cosmos-db.bicep' = {
5447
logAnalyticsWorkspaceName: logAnalyticsWorkspaceName
5548
debugUserPrincipalId: debugUserPrincipalId
5649
privateEndpointSubnetResourceId: privateEndpointSubnetResourceId
57-
existingAgentUserManagedIdentityName: agentUserManagedIdentity.name
5850
}
5951
}
6052

@@ -68,7 +60,6 @@ module deployAzureAISearchService 'ai-search.bicep' = {
6860
logAnalyticsWorkspaceName: logAnalyticsWorkspaceName
6961
debugUserPrincipalId: debugUserPrincipalId
7062
privateEndpointSubnetResourceId: privateEndpointSubnetResourceId
71-
existingAgentUserManagedIdentityName: agentUserManagedIdentity.name
7263
}
7364
}
7465

@@ -77,4 +68,3 @@ module deployAzureAISearchService 'ai-search.bicep' = {
7768
output cosmosDbAccountName string = deployCosmosDbThreadStorageAccount.outputs.cosmosDbAccountName
7869
output storageAccountName string = deployAgentStorageAccount.outputs.storageAccountName
7970
output aiSearchName string = deployAzureAISearchService.outputs.aiSearchName
80-
output agentUserManagedIdentityName string = agentUserManagedIdentity.name
Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
targetScope = 'resourceGroup'
2+
3+
@description('This is the base name for each Azure resource name (6-8 chars)')
4+
@minLength(6)
5+
@maxLength(8)
6+
param baseName string
7+
8+
@description('The existing Agent version to target by the Foundry AI Agent Service application deployment.')
9+
@minLength(1)
10+
param agentVersion string = '1'
11+
12+
// ---- Existing resources ----
13+
14+
// Storage Blob Data Owner Role
15+
resource storageBlobDataOwnerRole 'Microsoft.Authorization/roleDefinitions@2022-04-01' existing = {
16+
name: 'b7e6dc6d-f1e8-4753-8033-0f276bb0955b'
17+
scope: subscription()
18+
}
19+
20+
// Storage Blob Data Contributor
21+
resource storageBlobDataContributorRole 'Microsoft.Authorization/roleDefinitions@2022-04-01' existing = {
22+
name: 'ba92f5b4-2d11-453d-a403-e96b0029c9fe'
23+
scope: subscription()
24+
}
25+
26+
// Cosmos DB Account Operator Role
27+
resource cosmosDbOperatorRole 'Microsoft.Authorization/roleDefinitions@2022-04-01' existing = {
28+
name: '230815da-be43-4aae-9cb4-875f7bd000aa'
29+
scope: subscription()
30+
}
31+
32+
@description('Existing Azure Cosmos DB account. Will be assigning Data Contributor role to the Foundry project\'s identity.')
33+
resource cosmosDbAccount 'Microsoft.DocumentDB/databaseAccounts@2024-12-01-preview' existing = {
34+
name: 'cdb-ai-agent-threads-${baseName}'
35+
36+
@description('Built-in Cosmos DB Data Contributor role that can be assigned to Entra identities to grant data access on a Cosmos DB database.')
37+
resource dataContributorRole 'sqlRoleDefinitions' existing = {
38+
name: '00000000-0000-0000-0000-000000000002'
39+
}
40+
}
41+
42+
resource azureAISearchServiceContributorRole 'Microsoft.Authorization/roleDefinitions@2022-04-01' existing = {
43+
name: '7ca78c08-252a-4471-8644-bb5ff32d4ba0'
44+
scope: subscription()
45+
}
46+
47+
resource azureAISearchIndexDataContributorRole 'Microsoft.Authorization/roleDefinitions@2022-04-01' existing = {
48+
name: '8ebe5a00-799e-43f5-93ac-243d3dce84a7'
49+
scope: subscription()
50+
}
51+
52+
resource agentStorageAccount 'Microsoft.Storage/storageAccounts@2025-06-01' existing = {
53+
name: 'stagent${baseName}'
54+
}
55+
56+
resource azureAiSearchService 'Microsoft.Search/searchServices@2025-02-01-preview' existing = {
57+
name: 'ais-ai-agent-vector-store-${baseName}'
58+
}
59+
60+
@description('The internal ID of the project is used in the Azure Storage blob containers and in the Cosmos DB collections.')
61+
#disable-next-line BCP053
62+
var workspaceId = foundry::project.properties.internalId
63+
var workspaceIdAsGuid = '${substring(workspaceId, 0, 8)}-${substring(workspaceId, 8, 4)}-${substring(workspaceId, 12, 4)}-${substring(workspaceId, 16, 4)}-${substring(workspaceId, 20, 12)}'
64+
65+
var scopeAllContainers = '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.DocumentDB/databaseAccounts/${cosmosDbAccount.name}/dbs/enterprise_memory'
66+
67+
// ---- New resources ----
68+
69+
@description('Existing Foundry account.')
70+
resource foundry 'Microsoft.CognitiveServices/accounts@2025-10-01-preview' existing = {
71+
name: 'aif${baseName}'
72+
73+
@description('Existing Foundry project. The application and deployment will be created as a child resource of this project.')
74+
resource project 'projects' existing = {
75+
name: 'projchat'
76+
77+
@description('Create agent application in Foundry Agent Service.')
78+
resource application 'applications' = {
79+
name: 'appchat'
80+
properties: {
81+
agents: [
82+
{
83+
agentName: 'baseline-chatbot-agent'
84+
}
85+
]
86+
#disable-next-line BCP078
87+
authorizationPolicy: {
88+
authorizationScheme: 'Default'
89+
}
90+
displayName: 'Example of an Agent Application that exposes a Foundry agent chat interface through a service endpoint'
91+
trafficRoutingPolicy: {
92+
protocol: 'FixedRatio'
93+
rules: [
94+
{
95+
deploymentId: ''
96+
description: 'Default rule routing all traffic'
97+
ruleId: 'default'
98+
trafficPercentage: 100
99+
}
100+
]
101+
}
102+
}
103+
104+
@description('Create agent application deployment in Foundry Agent Service.')
105+
resource deploymentApp 'agentDeployments' = {
106+
name: 'agentdeploychat'
107+
properties: {
108+
agents: [
109+
{
110+
agentName: 'baseline-chatbot-agent'
111+
agentVersion: agentVersion
112+
}
113+
]
114+
displayName: 'Example of an agent deployment that runs an Agent Application referencing a specific agent version.'
115+
deploymentType: 'Managed' // prompt-based agent deployment
116+
protocols: [
117+
{
118+
protocol: 'Responses'
119+
version: '1.0'
120+
}
121+
]
122+
}
123+
dependsOn: [
124+
agentBlobDataContributorAssignment
125+
agentBlobDataOwnerConditionalAssignment
126+
127+
agentAISearchContributorAssignment
128+
agentAISearchIndexDataContributorAssignment
129+
130+
agentDbCosmosDbOperatorAssignment
131+
agentContainersWriterSqlAssignment
132+
]
133+
}
134+
}
135+
}
136+
}
137+
138+
// Role assignments
139+
140+
@description('Grant the Foundry application agent identity Storage Account Blob Data Contributor user role permissions.')
141+
module agentBlobDataContributorAssignment './modules/storageAccountRoleAssignment.bicep' = {
142+
name: 'agentBlobDataContributorAssignmentDeploy'
143+
params: {
144+
roleDefinitionId: storageBlobDataContributorRole.id
145+
principalId: foundry::project::application.properties.defaultInstanceIdentity.clientId
146+
existingStorageAccountName: agentStorageAccount.name
147+
}
148+
}
149+
150+
@description('Grant the Foundry application agent identity the Storage Account Blob Data Owner user role permissions.')
151+
module agentBlobDataOwnerConditionalAssignment './modules/storageAccountRoleAssignment.bicep' = {
152+
name: 'agentBlobDataOwnerConditionalAssignmentDeploy'
153+
params: {
154+
roleDefinitionId: storageBlobDataOwnerRole.id
155+
principalId: foundry::project::application.properties.defaultInstanceIdentity.clientId
156+
existingStorageAccountName: agentStorageAccount.name
157+
conditionVersion: '2.0'
158+
condition: '((!(ActionMatches{\'Microsoft.Storage/storageAccounts/blobServices/containers/blobs/tags/read\'}) AND !(ActionMatches{\'Microsoft.Storage/storageAccounts/blobServices/containers/blobs/filter/action\'}) AND !(ActionMatches{\'Microsoft.Storage/storageAccounts/blobServices/containers/blobs/tags/write\'}) ) OR (@Resource[Microsoft.Storage/storageAccounts/blobServices/containers:name] StringStartsWithIgnoreCase \'${workspaceIdAsGuid}\'))'
159+
}
160+
}
161+
162+
@description('Grant the Foundry application agent identity AI Search Contributor user role permissions.')
163+
module agentAISearchContributorAssignment './modules/aiSearchRoleAssignment.bicep' = {
164+
name: 'agentAISearchContributorAssignmentDeploy'
165+
params: {
166+
roleDefinitionId: azureAISearchServiceContributorRole.id
167+
principalId: foundry::project::application.properties.defaultInstanceIdentity.clientId
168+
existingAISearchAccountName: azureAiSearchService.name
169+
}
170+
}
171+
172+
@description('Grant the Foundry application agent identity AI Search Data Contributor user role permissions.')
173+
module agentAISearchIndexDataContributorAssignment './modules/aiSearchRoleAssignment.bicep' = {
174+
name: 'agentAISearchIndexDataContributorAssignmentDeploy'
175+
params: {
176+
roleDefinitionId: azureAISearchIndexDataContributorRole.id
177+
principalId: foundry::project::application.properties.defaultInstanceIdentity.clientId
178+
existingAISearchAccountName: azureAiSearchService.name
179+
}
180+
}
181+
182+
@description('Grant the Foundry application agent identity Cosmos DB Db Operator user role permissions.')
183+
module agentDbCosmosDbOperatorAssignment './modules/cosmosdbRoleAssignment.bicep' = {
184+
name: 'agentDbCosmosDbOperatorAssignmentDeploy'
185+
params: {
186+
roleDefinitionId: cosmosDbOperatorRole.id
187+
principalId: foundry::project::application.properties.defaultInstanceIdentity.clientId
188+
existingCosmosDbAccountName: cosmosDbAccount.name
189+
}
190+
}
191+
192+
// Sql Role Assignments
193+
194+
@description('Assign the Foundry application agent identity the ability to read and write data in all collections within enterprise_memory database.')
195+
module agentContainersWriterSqlAssignment './modules/cosmosdbSqlRoleAssignment.bicep' = {
196+
name: 'agentContainersWriterSqlAssignmentDeploy'
197+
params: {
198+
roleDefinitionId: cosmosDbAccount::dataContributorRole.id
199+
principalId: foundry::project::application.properties.defaultInstanceIdentity.clientId
200+
existingCosmosDbAccountName: cosmosDbAccount.name
201+
existingCosmosDbName: 'enterprise_memory'
202+
existingCosmosCollectionTypeName: 'containers'
203+
scopeUserContainerId: scopeAllContainers
204+
}
205+
}
206+
207+
// ---- Outputs ----
208+
output agentApplicationBaseUrl string = foundry::project::application.properties.baseUrl

0 commit comments

Comments
 (0)