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
46 changes: 39 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -225,16 +225,16 @@ The AI agent definition would likely be deployed from your application's pipelin
$FOUNDRY_PROJECT_NAME="projchat"
$MODEL_CONNECTION_NAME="agent-model"
$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}"
$FOUNDRY_AGENT_CREATE_URL="https://${FOUNDRY_NAME}.services.ai.azure.com/api/projects/${FOUNDRY_PROJECT_NAME}/agents?api-version=2025-11-15-preview"
$FOUNDRY_AGENT_URL="https://${FOUNDRY_NAME}.services.ai.azure.com/api/projects/${FOUNDRY_PROJECT_NAME}/agents?api-version=2025-11-15-preview"

echo $BING_CONNECTION_ID
echo $MODEL_CONNECTION_NAME
echo $FOUNDRY_AGENT_CREATE_URL
echo $FOUNDRY_AGENT_URL
```

1. Deploy the agent.
1. Create the agent.

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

```powershell
# Use the agent definition on disk
Expand All @@ -248,15 +248,47 @@ The AI agent definition would likely be deployed from your application's pipelin

$chat_agent | ConvertTo-Json -Depth 10 | Set-Content .\chat-with-bing-output.json

# Deploy the agent
az rest -u $FOUNDRY_AGENT_CREATE_URL -m "post" --resource "https://ai.azure.com" -b @chat-with-bing-output.json
# Persist the agent
az rest -u $FOUNDRY_AGENT_URL -m "post" --resource "https://ai.azure.com" -b @chat-with-bing-output.json
Comment thread
ckittel marked this conversation as resolved.

# Capture the Agent's ID
$AGENT_ID="$(az rest -u $FOUNDRY_AGENT_CREATE_URL -m 'get' --resource 'https://ai.azure.com' --query last_id -o tsv)"
$AGENT_ID="$(az rest -u $FOUNDRY_AGENT_URL -m 'get' --resource 'https://ai.azure.com' --query last_id -o tsv)"

echo $AGENT_ID
```

| :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`.|
| :-------: | :------------------------- |

1. Publish the the Agent
Comment thread
ckittel marked this conversation as resolved.

*This step publishes the agent by creating a new application within the Foundry project and a corresponding deployment that references a specific agent version.*

```bash
az deployment group create -f ./infra-as-code/bicep/ai-foundry-appdeploy.bicep \
-g $RESOURCE_GROUP \
-n 'foundryAppDeploy' \
-p baseName=${BASE_NAME}
```

| :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. |
| :-------: | :------------------------- |

1. Verify the agent deployment is running

*This step verify the Foundry AI Agent Service deployment is runnning by invoking the agent application's responses endpoint.*

```powershell
$AGENT_BASE_URL="$(az deployment group show -g $RESOURCE_GROUP -n 'foundryAppDeploy' --query "properties.outputs.agentApplicationBaseUrl.value" -o tsv)"

$AGENT_RESPONSES_URL="${AGENT_BASE_URL}/protocols/openai/responses?api-version=2025-11-15-preview"

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}"
```

| :information: | The terminal displays the agent application’s response, verifying that the specified agent version is running inside the deployment. |
| :--------: | :------------------------- |

### 3. Test the agent from the Foundry portal in the playground. *Optional.*

| :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. |
Expand Down
2 changes: 1 addition & 1 deletion agents/chat-with-bing.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "baseline-chatbot-agent",
"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",
"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",
"definition": {
"kind": "prompt",
"model": "MODEL_CONNECTION_NAME",
Expand Down
25 changes: 0 additions & 25 deletions infra-as-code/bicep/ai-agent-blob-storage.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -22,28 +22,13 @@ param logAnalyticsWorkspaceName string
@minLength(1)
param privateEndpointSubnetResourceId string

@description('The existing User Managed Identity for the Foundry project.')
@minLength(1)
param existingAgentUserManagedIdentityName string

// ---- Existing resources ----

@description('Existing Agent User Managed Identity for the Foundry project.')
resource agentUserManagedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2025-01-31-preview' existing = {
name: existingAgentUserManagedIdentityName
}

resource storageBlobDataOwnerRole 'Microsoft.Authorization/roleDefinitions@2022-04-01' existing = {
name: 'b7e6dc6d-f1e8-4753-8033-0f276bb0955b'
scope: subscription()
}

// Storage Blob Data Contributor
resource storageBlobDataContributorRole 'Microsoft.Authorization/roleDefinitions@2022-04-01' existing = {
name: 'ba92f5b4-2d11-453d-a403-e96b0029c9fe'
scope: subscription()
}

resource blobStorageLinkedPrivateDnsZone 'Microsoft.Network/privateDnsZones@2024-06-01' existing = {
name: 'privatelink.blob.${environment().suffixes.storage}'
}
Expand Down Expand Up @@ -111,16 +96,6 @@ resource debugUserBlobDataOwnerAssignment 'Microsoft.Authorization/roleAssignmen
}
}

@description('Grant the Foundry project managed identity Storage Account Blob Data Contributor user role permissions.')
module projectBlobDataContributorAssignment './modules/storageAccountRoleAssignment.bicep' = {
name: 'projectBlobDataContributorAssignmentDeploy'
params: {
roleDefinitionId: storageBlobDataContributorRole.id
principalId: agentUserManagedIdentity.properties.principalId
existingStorageAccountName: agentStorageAccount.name
}
}

// Private endpoints

resource storagePrivateEndpoint 'Microsoft.Network/privateEndpoints@2024-05-01' = {
Expand Down
10 changes: 0 additions & 10 deletions infra-as-code/bicep/ai-agent-service-dependencies.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,6 @@ param privateEndpointSubnetResourceId string

// ---- New resources ----

@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.')
resource agentUserManagedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2025-01-31-preview' = {
name: 'mi-agent-${baseName}'
location: location
}

@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.')
module deployAgentStorageAccount 'ai-agent-blob-storage.bicep' = {
name: 'agentStorageAccountDeploy'
Expand All @@ -40,7 +34,6 @@ module deployAgentStorageAccount 'ai-agent-blob-storage.bicep' = {
logAnalyticsWorkspaceName: logAnalyticsWorkspaceName
debugUserPrincipalId: debugUserPrincipalId
privateEndpointSubnetResourceId: privateEndpointSubnetResourceId
existingAgentUserManagedIdentityName: agentUserManagedIdentity.name
}
}

Expand All @@ -54,7 +47,6 @@ module deployCosmosDbThreadStorageAccount 'cosmos-db.bicep' = {
logAnalyticsWorkspaceName: logAnalyticsWorkspaceName
debugUserPrincipalId: debugUserPrincipalId
privateEndpointSubnetResourceId: privateEndpointSubnetResourceId
existingAgentUserManagedIdentityName: agentUserManagedIdentity.name
}
}

Expand All @@ -68,7 +60,6 @@ module deployAzureAISearchService 'ai-search.bicep' = {
logAnalyticsWorkspaceName: logAnalyticsWorkspaceName
debugUserPrincipalId: debugUserPrincipalId
privateEndpointSubnetResourceId: privateEndpointSubnetResourceId
existingAgentUserManagedIdentityName: agentUserManagedIdentity.name
}
}

Expand All @@ -77,4 +68,3 @@ module deployAzureAISearchService 'ai-search.bicep' = {
output cosmosDbAccountName string = deployCosmosDbThreadStorageAccount.outputs.cosmosDbAccountName
output storageAccountName string = deployAgentStorageAccount.outputs.storageAccountName
output aiSearchName string = deployAzureAISearchService.outputs.aiSearchName
output agentUserManagedIdentityName string = agentUserManagedIdentity.name
208 changes: 208 additions & 0 deletions infra-as-code/bicep/ai-foundry-appdeploy.bicep
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
targetScope = 'resourceGroup'

@description('This is the base name for each Azure resource name (6-8 chars)')
@minLength(6)
@maxLength(8)
param baseName string

@description('The existing Agent version to target by the Foundry AI Agent Service application deployment.')
@minLength(1)
param agentVersion string = '1'

// ---- Existing resources ----

// Storage Blob Data Owner Role
resource storageBlobDataOwnerRole 'Microsoft.Authorization/roleDefinitions@2022-04-01' existing = {
name: 'b7e6dc6d-f1e8-4753-8033-0f276bb0955b'
scope: subscription()
}

// Storage Blob Data Contributor
resource storageBlobDataContributorRole 'Microsoft.Authorization/roleDefinitions@2022-04-01' existing = {
name: 'ba92f5b4-2d11-453d-a403-e96b0029c9fe'
scope: subscription()
}

// Cosmos DB Account Operator Role
resource cosmosDbOperatorRole 'Microsoft.Authorization/roleDefinitions@2022-04-01' existing = {
name: '230815da-be43-4aae-9cb4-875f7bd000aa'
scope: subscription()
}

@description('Existing Azure Cosmos DB account. Will be assigning Data Contributor role to the Foundry project\'s identity.')
resource cosmosDbAccount 'Microsoft.DocumentDB/databaseAccounts@2024-12-01-preview' existing = {
name: 'cdb-ai-agent-threads-${baseName}'

@description('Built-in Cosmos DB Data Contributor role that can be assigned to Entra identities to grant data access on a Cosmos DB database.')
resource dataContributorRole 'sqlRoleDefinitions' existing = {
name: '00000000-0000-0000-0000-000000000002'
}
}

resource azureAISearchServiceContributorRole 'Microsoft.Authorization/roleDefinitions@2022-04-01' existing = {
name: '7ca78c08-252a-4471-8644-bb5ff32d4ba0'
scope: subscription()
}

resource azureAISearchIndexDataContributorRole 'Microsoft.Authorization/roleDefinitions@2022-04-01' existing = {
name: '8ebe5a00-799e-43f5-93ac-243d3dce84a7'
scope: subscription()
}

resource agentStorageAccount 'Microsoft.Storage/storageAccounts@2025-06-01' existing = {
name: 'stagent${baseName}'
}

resource azureAiSearchService 'Microsoft.Search/searchServices@2025-02-01-preview' existing = {
name: 'ais-ai-agent-vector-store-${baseName}'
}

@description('The internal ID of the project is used in the Azure Storage blob containers and in the Cosmos DB collections.')
#disable-next-line BCP053
var workspaceId = foundry::project.properties.internalId
var workspaceIdAsGuid = '${substring(workspaceId, 0, 8)}-${substring(workspaceId, 8, 4)}-${substring(workspaceId, 12, 4)}-${substring(workspaceId, 16, 4)}-${substring(workspaceId, 20, 12)}'

var scopeAllContainers = '/subscriptions/${subscription().subscriptionId}/resourceGroups/${resourceGroup().name}/providers/Microsoft.DocumentDB/databaseAccounts/${cosmosDbAccount.name}/dbs/enterprise_memory'

// ---- New resources ----

@description('Existing Foundry account.')
resource foundry 'Microsoft.CognitiveServices/accounts@2025-10-01-preview' existing = {
name: 'aif${baseName}'

@description('Existing Foundry project. The application and deployment will be created as a child resource of this project.')
resource project 'projects' existing = {
name: 'projchat'

@description('Create agent application in Foundry Agent Service.')
resource application 'applications' = {
name: 'appchat'
properties: {
agents: [
{
agentName: 'baseline-chatbot-agent'
}
]
#disable-next-line BCP078
authorizationPolicy: {
authorizationScheme: 'Default'
}
displayName: 'Example of an Agent Application that exposes a Foundry agent chat interface through a service endpoint'
trafficRoutingPolicy: {
protocol: 'FixedRatio'
rules: [
{
deploymentId: ''
description: 'Default rule routing all traffic'
ruleId: 'default'
trafficPercentage: 100
}
]
}
}

@description('Create agent application deployment in Foundry Agent Service.')
resource deploymentApp 'agentDeployments' = {
name: 'agentdeploychat'
properties: {
agents: [
{
agentName: 'baseline-chatbot-agent'
agentVersion: agentVersion
}
]
displayName: 'Example of an agent deployment that runs an Agent Application referencing a specific agent version.'
deploymentType: 'Managed' // prompt-based agent deployment
protocols: [
{
protocol: 'Responses'
version: '1.0'
Comment thread
ckittel marked this conversation as resolved.
}
]
}
dependsOn: [
agentBlobDataContributorAssignment
agentBlobDataOwnerConditionalAssignment

agentAISearchContributorAssignment
agentAISearchIndexDataContributorAssignment

agentDbCosmosDbOperatorAssignment
agentContainersWriterSqlAssignment
]
}
}
}
}

// Role assignments

@description('Grant the Foundry application agent identity Storage Account Blob Data Contributor user role permissions.')
module agentBlobDataContributorAssignment './modules/storageAccountRoleAssignment.bicep' = {
name: 'agentBlobDataContributorAssignmentDeploy'
params: {
roleDefinitionId: storageBlobDataContributorRole.id
principalId: foundry::project::application.properties.defaultInstanceIdentity.clientId
existingStorageAccountName: agentStorageAccount.name
}
}

@description('Grant the Foundry application agent identity the Storage Account Blob Data Owner user role permissions.')
module agentBlobDataOwnerConditionalAssignment './modules/storageAccountRoleAssignment.bicep' = {
name: 'agentBlobDataOwnerConditionalAssignmentDeploy'
params: {
roleDefinitionId: storageBlobDataOwnerRole.id
principalId: foundry::project::application.properties.defaultInstanceIdentity.clientId
existingStorageAccountName: agentStorageAccount.name
conditionVersion: '2.0'
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}\'))'
}
}

@description('Grant the Foundry application agent identity AI Search Contributor user role permissions.')
module agentAISearchContributorAssignment './modules/aiSearchRoleAssignment.bicep' = {
name: 'agentAISearchContributorAssignmentDeploy'
params: {
roleDefinitionId: azureAISearchServiceContributorRole.id
principalId: foundry::project::application.properties.defaultInstanceIdentity.clientId
existingAISearchAccountName: azureAiSearchService.name
}
}

@description('Grant the Foundry application agent identity AI Search Data Contributor user role permissions.')
module agentAISearchIndexDataContributorAssignment './modules/aiSearchRoleAssignment.bicep' = {
name: 'agentAISearchIndexDataContributorAssignmentDeploy'
params: {
roleDefinitionId: azureAISearchIndexDataContributorRole.id
principalId: foundry::project::application.properties.defaultInstanceIdentity.clientId
existingAISearchAccountName: azureAiSearchService.name
}
}

@description('Grant the Foundry application agent identity Cosmos DB Db Operator user role permissions.')
module agentDbCosmosDbOperatorAssignment './modules/cosmosdbRoleAssignment.bicep' = {
name: 'agentDbCosmosDbOperatorAssignmentDeploy'
params: {
roleDefinitionId: cosmosDbOperatorRole.id
principalId: foundry::project::application.properties.defaultInstanceIdentity.clientId
existingCosmosDbAccountName: cosmosDbAccount.name
}
}

// Sql Role Assignments

@description('Assign the Foundry application agent identity the ability to read and write data in all collections within enterprise_memory database.')
module agentContainersWriterSqlAssignment './modules/cosmosdbSqlRoleAssignment.bicep' = {
name: 'agentContainersWriterSqlAssignmentDeploy'
params: {
roleDefinitionId: cosmosDbAccount::dataContributorRole.id
principalId: foundry::project::application.properties.defaultInstanceIdentity.clientId
existingCosmosDbAccountName: cosmosDbAccount.name
existingCosmosDbName: 'enterprise_memory'
existingCosmosCollectionTypeName: 'containers'
scopeUserContainerId: scopeAllContainers
}
}

// ---- Outputs ----
output agentApplicationBaseUrl string = foundry::project::application.properties.baseUrl
Loading