This sample demonstrates how to build and deploy an agentic AI application using Radius, an open-source application platform that enables developers and platform engineers to define, deploy, and manage cloud-native applications across any infrastructure.
Building AI applications today is hard. A developer who wants to build an AI agent goes through the toil of understanding infrastructure dependencies like Azure OpenAI deployments, configuring AI Search indexes, setting up managed identities with the right RBAC roles, provisioning storage accounts and more over conforming to the requirements of the enterprise. This creates a high barrier to entry for developers and slows down innovation.
Radius solves this by enabling platform engineers to define abstract, application-oriented Resource Types and, separately, Recipes which implement those Resource Types using Infrastructure as Code (IaC). The developer just declares what application resources(an AI agent, a database, a frontend) they need in an application definition and Radius handles the deployment of the resources.
This sample is a customer support agent application for the fictional Contoso Online Store. Unlike a simple chatbot, this agent autonomously reasons about customer requests, decides which tools to use, takes actions (like cancelling orders or initiating returns), and chains multiple operations together. It uses several Azure services including Azure OpenAI.
Below is a high-level architecture diagram of the application
At the completion of this walkthrough, the application and Radius configuration will look like:
├── knowledge-base/ # Contoso policy PDFs for RAG
├── scripts/setup-azure.sh # One-command Azure prerequisite setup
├── src/
│ ├── agent-runtime/ # Agentic backend (FastAPI + OpenAI tool calling)
│ └── web/ # Chat UI frontend (nginx)
└── radius/
├── app.bicep # Application definition (what the developer writes)
├── env.bicep # Environment + shared resources
├── types/ # Custom resource type schemas (AI, Data, Storage)
├── extensions/ # Generated Bicep extensions (.tgz)
└── recipes/ # IaC templates (agent, postgres, blobstorage)
By the end of this walkthrough, you will:
- Understand the Radius concepts of Resource Types, Recipes, Environments, and Applications
- See how custom abstractions (like
Radius.AI/agents) simplify the developer experience building agentic applications - Deploy a fully functional agentic AI application to Azure with Radius
Before you begin, you need:
- An Azure subscription
- Azure CLI installed
- Radius CLI installed
- kubectl installed
Tip
This walkthrough uses Bash syntax. On Windows, use one of:
- WSL (recommended)
- Git Bash
- Azure Cloud Shell
PowerShell users can follow along with minor syntax adjustments (examples provided where needed).
git clone https://github.com/Reshrahim/customer-agent.git
cd customer-agentRun the setup script to create an Azure resource group, AKS cluster, service principal, and register the resource providers required for this application:
Bash
./scripts/setup-azure.sh --location westus3 --resource-group customer-agent --cluster-name customer-agent-aksPowerShell
bash ./scripts/setup-azure.sh --location westus3 --resource-group customer-agent --cluster-name customer-agent-aksNote
This takes a few minutes (mostly AKS cluster creation).
--resource-group <name>name of the resource group to create. The script will create one if it doesn't exist. Defaults tocustomer-agent.--location <region>region for the resource group and AKS cluster. Defaults towestus3.--cluster-name <your-cluster-name>name of the AKS cluster. The script will detect it and skip creation if it already exists. Defaults tocustomer-agent-aks.
The script will output the next steps. Service principal credentials are saved to .azure-sp.env for use in the next step.
What the script does (click to expand)
- Registers required Azure resource providers (Storage, PostgreSQL, ContainerInstance, OperationalInsights, Search, CognitiveServices)
- Creates a resource group
- Creates an AKS cluster with 1 node (
Standard_B2as_v2) - Creates a service principal with "Owner" role scoped to the resource group
- Saves credentials to
.azure-sp.env
Verify the current kubectl context is set to the AKS cluster created in the previous step:
kubectl config current-contextInstall Radius in your AKS cluster:
rad install kubernetesVerify the installation:
kubectl get pods -n radius-systemYou should see all Radius pods running:
NAME READY STATUS RESTARTS AGE
applications-rp 1/1 Running 0 1m
bicep-de 1/1 Running 0 1m
contour-contour 1/1 Running 0 1m
contour-envoy 1/1 Running 0 1m
controller 1/1 Running 0 1m
dashboard 1/1 Running 0 1m
dynamic-rp 1/1 Running 0 1m
ucp 1/1 Running 0 1m
Register the Azure credential using the service principal from Step 2:
Bash
source .azure-sp.env && rad credential register azure sp \
--client-id $AZURE_CLIENT_ID \
--client-secret $AZURE_CLIENT_SECRET \
--tenant-id $AZURE_TENANT_IDPowerShell
Get-Content .azure-sp.env | ForEach-Object {
$name, $value = $_ -split '=', 2
Set-Item -Path Env:$name -Value $value
}
rad credential register azure sp `
--client-id $env:AZURE_CLIENT_ID `
--client-secret $env:AZURE_CLIENT_SECRET `
--tenant-id $env:AZURE_TENANT_IDVerify the credential is registered:
rad credential listPROVIDER REGISTERED
azure true
Resource Types are abstract application resources that are infrastructure/cloud provider-agnostic. They define the properties that developers can set when they declare resources in their app.bicep, and they map to recipes that provision the underlying infrastructure
Register all three types with Radius:
rad resource-type create -f radius/types/agent.yaml
rad resource-type create -f radius/types/postgreSqlDatabases.yaml
rad resource-type create -f radius/types/blobStorages.yamlEach type definition in radius/types/ is a YAML file that declares:
- A namespace and type name (e.g.,
Radius.AI/agents) - A schema describing the properties developers can set (like
prompt,model,enableObservability) - Read-only properties that recipes output back (like
agentEndpoint)
For example, radius/types/agent.yaml defines the Radius.AI/agents type. A developer using this type only needs to specify a prompt and model name — they don't need to know that behind the scenes, the recipe provisions 8 Azure resources with role assignments and networking.
You can verify the types were created:
rad resource-type listTYPE NAMESPACE APIVERSION
Applications.Core/applications Applications.Core ["2023-10-01-preview"]
...
Radius.AI/agents Radius.AI ["2025-08-01-preview"]
Radius.Data/postgreSqlDatabases Radius.Data ["2025-08-01-preview"]
Radius.Storage/blobStorages Radius.Storage ["2025-08-01-preview"]
Learn about Bicep extensions (click to expand)
Bicep extensions are needed for each Resource Type to provide type safety and autocompletion in VS Code (when the Bicep extension is installed). These extensions are defined in the bicepconfig.json file. As part of this sample, a preconfigured bicepconfig.json referencing the pre-generated Bicep extensions in the radius/extensions/ directory is provided. No action needed.
If you modify a Resource Type definition, you must regenerate its extension:
rad bicep publish-extension -f radius/types/agent.yaml --target radius/extensions/radiusai.tgz
rad bicep publish-extension -f radius/types/postgreSqlDatabases.yaml --target radius/extensions/radiusdata.tgz
rad bicep publish-extension -f radius/types/blobStorages.yaml --target radius/extensions/radiusstorage.tgzImportant
No action is needed in this step. The recipes have already been published to ghcr.io/reshrahim/recipes/.
A Recipe defines how to provision a Resource Type. Recipes are Infrastructure as Code templates, Bicep in this sample, that Radius executes when you deploy a resource of a given type. They receive context from Radius (the resource name, properties, connections) and output infrastructure.
This sample has three recipes:
| Recipe | Resource Type |
|---|---|
recipes/agent.bicep |
Radius.AI/agents |
recipes/postgres.bicep |
Radius.Data/postgreSqlDatabases |
recipes/blobstorage.bicep |
Radius.Storage/blobStorages |
Developer never sees these recipes. They just declare resource agent 'Radius.AI/agents' = { ... } in their app.bicep, and Radius automatically finds and executes the matching recipe configured by the platform engineer in the environment.
Learn about making changes to recipes (click to expand)
Bicep templates are published to OCI registries (like container images). If you make changes, republish them to your own registry:
rad bicep publish \
--file radius/recipes/agent.bicep \
--target br:ghcr.io/<org-name>/recipes/agent:1.0
rad bicep publish \
--file radius/recipes/postgres.bicep \
--target br:ghcr.io/<org-name>/recipes/postgres:1.0
rad bicep publish \
--file radius/recipes/blobstorage.bicep \
--target br:ghcr.io/<org-name>/recipes/blobstorage:1.0A Radius Environment is where you configure which recipes to use and where Azure resources should be provisioned. Just like Azure, all Radius resources are created in a Radius resource group.
Create a Radius resource group
rad group create azureNow deploy the environment. This will create the environment and register the recipes that define how to provision the Resource Types you created in the previous step.
Bash
source .azure-sp.env && rad deploy radius/env.bicep --group azure \
--parameters azureSubscriptionId=$AZURE_SUBSCRIPTION_ID \
--parameters azureResourceGroup=$AZURE_RESOURCE_GROUPPowerShell
Get-Content .azure-sp.env | ForEach-Object {
$name, $value = $_ -split '=', 2
Set-Item -Path Env:$name -Value $value
}
rad deploy radius/env.bicep --group azure `
--parameters azureSubscriptionId=$env:AZURE_SUBSCRIPTION_ID `
--parameters azureResourceGroup=$env:AZURE_RESOURCE_GROUPCreate a workspace so the rad CLI knows which environment and group to use by default:
Bash
rad workspace create kubernetes azure \
--context $(kubectl config current-context) \
--environment azure \
--group azurePowerShell
rad workspace create kubernetes azure `
--context (kubectl config current-context) `
--environment azure `
--group azureConfirm the environment was created:
rad environment show -o jsonConfirm the recipes were added to the environment:
rad recipe listRECIPE TYPE TEMPLATE KIND TEMPLATE
default Radius.AI/agents bicep ghcr.io/reshrahim/recipes/agent:1.0
default Radius.Data/postgreSqlDatabases bicep ghcr.io/reshrahim/recipes/postgres:1.0
default Radius.Storage/blobStorages bicep ghcr.io/reshrahim/recipes/blobstorage:1.0
The azure Radius environment needs to have some shared resources which are shared across all applications deployed to Radius environment. This includes:
- A PostgreSQL database which is the order database for the Contoso Online Store
- A blob storage account which contains store policies for the Contoso Online Store
Deploy these shared resources:
rad deploy radius/shared-resources.bicepThe deployment will take approximately 10 minutes. When complete, the output should be similar to:
Deployment In Progress...
Completed azure Applications.Core/environments
Completed contoso-db Radius.Data/postgreSqlDatabases
Completed contoso-knowledge-base Radius.Storage/blobStorages
Deployment Complete
Resources:
contoso-db Radius.Data/postgreSqlDatabases
contoso-knowledge-base Radius.Storage/blobStoragesNow developers can reference these pre-existing shared resources by name using the existing keyword:
resource postgresql 'Radius.Data/postgreSqlDatabases@2025-08-01-preview' existing = {
name: 'contoso-db'
}The agent uses Azure AI Search for knowledge retrieval (RAG). The search index is populated by an indexer that reads documents from the blob storage container. In this step, you upload the Contoso policy documents, so the agent can answer questions about shipping, returns, and the loyalty program.
The knowledge-base/ folder contains three PDF documents:
contoso-shipping-policy.pdfcontoso-return-refund-policy.pdfcontoso-loyalty-program.pdf
Upload them to the blob storage account that was provisioned in the previous step:
Bash
Get the storage account name (provisioned by the blobstorage recipe):
STORAGE_ACCOUNT=$(az storage account list --resource-group customer-agent \
--query "[?tags.\"radius-resource-type\"=='Radius.Storage/blobStorages'].name" -o tsv)Upload all PDFs to the documents container:
az storage blob upload-batch \
--account-name "$STORAGE_ACCOUNT" \
--destination documents \
--source knowledge-base/ \
--pattern "*.pdf" \
--overwritePowerShell
Get the storage account name (provisioned by the blobstorage recipe):
$STORAGE_ACCOUNT = az storage account list `
--resource-group customer-agent `
--query "[?tags.`"radius-resource-type`"=='Radius.Storage/blobStorages'].name" `
-o tsvUpload all PDFs to the 'documents' container:
az storage blob upload-batch `
--account-name $STORAGE_ACCOUNT `
--destination documents `
--source knowledge-base/ `
--pattern "*.pdf" `
--overwriteOnce uploaded, the AI Search indexer (created by the agent recipe in the next step) will automatically index these documents every 5 minutes.
Deploy the application:
rad deploy radius/app.bicepThe deployment will take approximately 15-20 minutes to complete. In the meantime, examine the app.bicep file:
- It references shared resources (
postgresql,blobstorage) using theexistingkeyword - It creates a
Radius.AI/agentsresource with a system prompt, model name, and connections to both shared resources - It creates a
frontend-uicontainer connected to the agent
When the deployment is complete, you should see output similar to:
Deployment In Progress...
Completed contoso-support-agent Applications.Core/applications
Completed contoso-db Radius.Data/postgreSqlDatabases
Completed contoso-knowledge-base Radius.Storage/blobStorages
Completed support-agent Radius.AI/agents
Completed frontend-ui Applications.Core/containers
Deployment Complete
Resources:
contoso-support-agent Applications.Core/applications
frontend-ui Applications.Core/containers
support-agent Radius.AI/agents
Port-forward the frontend service to your local machine:
kubectl port-forward svc/frontend-ui 3000:3000 -n azure-contoso-support-agentOpen http://localhost:3000 in your browser.
Here are some things to try that demonstrate the agentic behavior:
Simple order lookup (single tool call):
What's the status of ORD-10001?
The agent will call lookup_order and respond with the order details from the database.
Policy question (knowledge base search):
What's your return policy for electronics?
The agent will call search_knowledge_base and respond using information from the uploaded PDF documents.
Multi-step tool chaining (multiple tool calls in sequence):
I want to return the headphones from order ORD-10001. Can you help?
The agent will:
- Call
lookup_orderto get the order details - Call
check_return_eligibilityto verify the order is within the return window - Tell you the eligibility result and ask for confirmation
- After you confirm, call
initiate_returnto create the return record
Escalation (knowing when to hand off):
This is unacceptable, I've been waiting 3 weeks and nobody can help me. I want to speak to a manager.
The agent will recognize the customer's frustration and call create_support_ticket to escalate to a human agent.
-
Delete the Azure resource group:
az group delete --name customer-agent --yes
-
Delete the service principal:
az ad sp delete --id $(az ad sp list --display-name "radius-sp" --query "[0].id" -o tsv) -
Delete the credential file:
rm .azure-sp.env
-
Purge your resources from Azure
az cognitiveservices account purge \ --name support-agent-openai \ --resource-group customer-agent \ --location westus3
-
Delete your Radius workspace:
rad workspace delete azure --yes

