diff --git a/.github/workflows/azure-dev.yml b/.github/workflows/azure-dev.yml
index 4fd01d4f..9404fe6f 100644
--- a/.github/workflows/azure-dev.yml
+++ b/.github/workflows/azure-dev.yml
@@ -23,12 +23,14 @@ env:
AZURE_RESOURCE_GROUP: ${{ vars.AZURE_RESOURCE_GROUP }}
AZURE_DEV_USER_AGENT: ${{ secrets.AZURE_DEV_USER_AGENT }}
# Existing resources, when applicable
+ AZURE_APP_SERVICE_SKU: ${{ vars.azureAppServicePlanSku }}
AZURE_OPENAI_SERVICE: ${{ vars.AZURE_OPENAI_SERVICE }}
AZURE_OPENAI_RESOURCE_GROUP: ${{ vars.AZURE_OPENAI_RESOURCE_GROUP }}
AZURE_FORMRECOGNIZER_SERVICE: ${{ vars.AZURE_FORMRECOGNIZER_SERVICE }}
AZURE_FORMRECOGNIZER_RESOURCE_GROUP: ${{ vars.AZURE_FORMRECOGNIZER_RESOURCE_GROUP }}
AZURE_SEARCH_SERVICE: ${{ vars.AZURE_SEARCH_SERVICE }}
AZURE_SEARCH_SERVICE_RESOURCE_GROUP: ${{ vars.AZURE_SEARCH_SERVICE_RESOURCE_GROUP }}
+ AZURE_SEARCH_SERVICE_SKU: ${{ vars.AZURE_SEARCH_SERVICE_SKU }}
AZURE_STORAGE_ACCOUNT: ${{ vars.AZURE_STORAGE_ACCOUNT }}
AZURE_STORAGE_RESOURCE_GROUP: ${{ vars.AZURE_STORAGE_RESOURCE_GROUP }}
AZURE_KEY_VAULT_NAME: ${{ vars.AZURE_KEY_VAULT_NAME }}
diff --git a/README.md b/README.md
index cc2ac6e8..5f8a052e 100644
--- a/README.md
+++ b/README.md
@@ -97,7 +97,7 @@ In order to deploy and run this example, you'll need
- **Azure account permissions** - Your Azure Account must have `Microsoft.Authorization/roleAssignments/write` permissions, such as [User Access Administrator](https://learn.microsoft.com/azure/role-based-access-control/built-in-roles#user-access-administrator) or [Owner](https://learn.microsoft.com/azure/role-based-access-control/built-in-roles#owner).
> [!WARNING]
-> By default this sample will create an Azure Container App, and Azure AI Search resource that have a monthly cost, as well as Form Recognizer resource that has cost per document page. You can switch them to free versions of each of them if you want to avoid this cost by changing the parameters file under the infra folder (though there are some limits to consider; for example, you can have up to 1 free Azure AI Search resource per subscription, and the free Form Recognizer resource only analyzes the first 2 pages of each document.)
+> By default this sample will create an Azure Container App, and Azure AI Search resource that have a monthly cost, as well as Azure AI Document Intelligence resource that has cost per document page. You can switch them to free versions of each of them if you want to avoid this cost by changing the parameters file under the infra folder (though there are some limits to consider; for example, you can have up to 1 free Azure AI Search resource per subscription, and the free Azure AI Document Intelligence resource only analyzes the first 2 pages of each document.)
### Project setup
@@ -358,11 +358,13 @@ Pricing varies per region and usage, so it isn't possible to predict exact costs
- [**Azure Container Apps**](https://azure.microsoft.com/pricing/details/container-apps/)
- [**Azure OpenAI Service**](https://azure.microsoft.com/pricing/details/cognitive-services/openai-service/)
-- [**Azure Form Recognizer**](https://azure.microsoft.com/pricing/details/form-recognizer/)
+- [**Azure AI Document Intelligence**](https://azure.microsoft.com/pricing/details/ai-document-intelligence/)
- [**Azure AI Search**](https://azure.microsoft.com/pricing/details/search/)
- [**Azure Blob Storage**](https://azure.microsoft.com/pricing/details/storage/blobs/)
- [**Azure Monitor**](https://azure.microsoft.com/pricing/details/monitor/)
+To reduce costs, you can switch to free SKUs for various services, but those SKUs have limitations. See this [guide on deploying with minimal costs](./docs/deploy_lowcost.md) for more details.
+
## Resources
- [Revolutionize your Enterprise Data with ChatGPT: Next-gen Apps w/ Azure OpenAI and Azure AI Search](https://aka.ms/entgptsearchblog)
diff --git a/app/prepdocs/PrepareDocs/Program.Options.cs b/app/prepdocs/PrepareDocs/Program.Options.cs
index 71f775ed..b7f0eaba 100644
--- a/app/prepdocs/PrepareDocs/Program.Options.cs
+++ b/app/prepdocs/PrepareDocs/Program.Options.cs
@@ -39,7 +39,7 @@ internal static partial class Program
new(name: "--removeall", description: "Remove all blobs from blob storage and documents from the search index");
private static readonly Option s_formRecognizerServiceEndpoint =
- new(name: "--formrecognizerendpoint", description: "Optional. The Azure Form Recognizer service endpoint which will be used to extract text, tables and layout from the documents (must exist already)");
+ new(name: "--formrecognizerendpoint", description: "Optional. The Azure AI Document Intelligence service endpoint which will be used to extract text, tables and layout from the documents (must exist already)");
private static readonly Option s_computerVisionServiceEndpoint =
new(name: "--computervisionendpoint", description: "Optional. The Azure Computer Vision service endpoint which will be used to vectorize image and query");
diff --git a/app/shared/Shared/Services/AzureSearchEmbedService.cs b/app/shared/Shared/Services/AzureSearchEmbedService.cs
index ab31d2b8..880d9e04 100644
--- a/app/shared/Shared/Services/AzureSearchEmbedService.cs
+++ b/app/shared/Shared/Services/AzureSearchEmbedService.cs
@@ -194,7 +194,7 @@ public async Task EnsureSearchIndexAsync(string searchIndexName, CancellationTok
public async Task> GetDocumentTextAsync(Stream blobStream, string blobName)
{
logger?.LogInformation(
- "Extracting text from '{Blob}' using Azure Form Recognizer", blobName);
+ "Extracting text from '{Blob}' using Azure AI Document Intelligence", blobName);
using var ms = new MemoryStream();
blobStream.CopyTo(ms);
diff --git a/docs/deploy_lowcost.md b/docs/deploy_lowcost.md
new file mode 100644
index 00000000..60638a5e
--- /dev/null
+++ b/docs/deploy_lowcost.md
@@ -0,0 +1,64 @@
+# Deploying with Minimal Costs
+
+This AI RAG chat application is designed to be easily deployed using the Azure Developer CLI, which provisions the infrastructure according to the Bicep files in the `infra` folder. Those files describe each of the Azure resources needed, and configures their SKU (pricing tier) and other parameters. Many Azure services offer a free tier, but the infrastructure files in this project do *not* default to the free tier as there are often limitations in that tier.
+
+However, if your goal is to minimize costs while prototyping your application, follow the steps below *before* running `azd up`. Once you've gone through these steps, return to the [deployment steps](../README.md#deployment).
+
+[📺 Live stream: Deploying from a free account](https://youtu.be/V1ZLzXU4iiw)
+
+1. Log in to your Azure account using the Azure Developer CLI:
+
+ ```shell
+ azd auth login
+ ```
+
+1. Create a new azd environment for the free resource group:
+
+ ```shell
+ azd env new
+ ```
+
+ Enter a name that will be used for the resource group.
+ This will create a new folder in the `.azure` folder, and set it as the active environment for any calls to `azd` going forward.
+
+1. Use the free tier of **Azure AI Document Intelligence** (previously known as [Form Recognizer](https://learn.microsoft.com/en-us/azure/ai-services/document-intelligence/overview?view=doc-intel-4.0.0)):
+
+ ```shell
+ azd env set AZURE_FORMRECOGNIZER_SERVICE_SKU F0
+ ```
+
+1. Use the free tier of **Azure AI Search**:
+
+ ```shell
+ azd env set AZURE_SEARCH_SERVICE_SKU free
+ azd env set AZURE_SEARCH_SEMANTIC_RANKER disabled
+ ```
+
+ Limitations:
+ 1. You are only allowed one free search service across all regions.
+ If you have one already, either delete that service or follow instructions to
+ reuse your [existing search service](../README.md#use-existing-resources).
+ 2. The free tier does not support semantic ranker. Note that will generally result in [decreased search relevance](https://techcommunity.microsoft.com/t5/ai-azure-ai-services-blog/azure-ai-search-outperforming-vector-search-with-hybrid/ba-p/3929167).
+
+1. Turn off **Azure Monitor** (Application Insights):
+
+ ```shell
+ azd env set AZURE_USE_APPLICATION_INSIGHTS false
+ ```
+
+ Application Insights is quite inexpensive already, so turning this off may not be worth the costs saved, but it is an option for those who want to minimize costs.
+
+1. (Optional) Use **OpenAI.com** instead of Azure OpenAI.
+
+ You can create a free account in OpenAI and [request a key to use OpenAI models](https://platform.openai.com/docs/quickstart/create-and-export-an-api-key). Once you have this, you can disable the use of Azure OpenAI Services, and use OpenAI APIs.
+
+ ```shell
+ azd env set USE_AOAI false
+ azd env set USE_VISION false
+ azd env set OPENAI_CHATGPT_DEPLOYMENT gpt-4o-mini
+ azd env set OPENAI_API_KEY
+ ```
+
+ ***Note:** Both Azure OpenAI and openai.com OpenAI accounts will incur costs, based on tokens used, but the costs are fairly low for the amount of sample data (less than $10).*
+
+1. Once you've made the desired customizations, follow the steps in the README [to run `azd up`](../README.md#deploying-from-scratch). We recommend using "eastus" as the region, for availability reasons.
\ No newline at end of file
diff --git a/infra/app/web.bicep b/infra/app/web.bicep
index bd9cdf61..af0bd22c 100644
--- a/infra/app/web.bicep
+++ b/infra/app/web.bicep
@@ -41,7 +41,7 @@ param searchServiceEndpoint string
@description('The search index name')
param searchIndexName string
-@description('The Form Recognizer endpoint')
+@description('The Azure AI Document Intelligence endpoint')
param formRecognizerEndpoint string
@description('The Computer Vision endpoint')
diff --git a/infra/main.bicep b/infra/main.bicep
index 11fb37bc..79010b0d 100644
--- a/infra/main.bicep
+++ b/infra/main.bicep
@@ -27,7 +27,10 @@ param azureOpenAIChatGptModelName string = 'gpt-4o-mini'
@allowed([ '0613', '2024-07-18' ])
param azureOpenAIChatGptModelVersion string ='2024-07-18'
-@description('Name of the Azure Application Insights dashboard')
+@description('Defines if the process will deploy an Azure Application Insights resource')
+param useApplicationInsights bool = true
+
+// @description('Name of the Azure Application Insights dashboard')
param applicationInsightsDashboardName string = ''
@description('Name of the Azure Application Insights resource')
@@ -72,16 +75,17 @@ param containerRegistryName string = ''
@description('Name of the resource group for the Azure container registry')
param containerRegistryResourceGroupName string = ''
-@description('Location of the resource group for the Form Recognizer service')
+@description('Location of the resource group for the Azure AI Document Intelligence service')
param formRecognizerResourceGroupLocation string = location
-@description('Name of the resource group for the Form Recognizer service')
+@description('Name of the resource group for the Azure AI Document Intelligence service')
param formRecognizerResourceGroupName string = ''
-@description('Name of the Form Recognizer service')
+@description('Name of the Azure AI Document Intelligence service')
param formRecognizerServiceName string = ''
-@description('SKU name for the Form Recognizer service. Default: S0')
+@description('SKU name for the Azure AI Document Intelligence service. Default: S0')
+@allowed([ 'S0', 'F0' ])
param formRecognizerSkuName string = 'S0'
@description('Name of the Azure Function App')
@@ -129,9 +133,14 @@ param searchServiceResourceGroupLocation string = location
@description('Name of the resource group for the Azure AI Search service')
param searchServiceResourceGroupName string = ''
+@description('Azure AI Search Semantic Ranker Level')
+param searchServiceSemanticRankerLevel string // Set in main.parameters.json
+
@description('SKU name for the Azure AI Search service. Default: standard')
param searchServiceSkuName string = 'standard'
+var actualSearchServiceSemanticRankerLevel = (searchServiceSkuName == 'free') ? 'disabled' : searchServiceSemanticRankerLevel
+
@description('Name of the storage account')
param storageAccountName string = ''
@@ -168,7 +177,7 @@ param openAiChatGptDeployment string
@description('OpenAI Embedding Model')
param openAiEmbeddingDeployment string
-@description('Use Vision retrival. default: false')
+@description('Use Vision retrieval. default: false')
param useVision bool = false
var abbrs = loadJsonContent('./abbreviations.json')
@@ -177,7 +186,6 @@ var resourceToken = toLower(uniqueString(subscription().id, environmentName, loc
var baseTags = { 'azd-env-name': environmentName }
var updatedTags = union(empty(tags) ? {} : base64ToJson(tags), baseTags)
-
// Organize resources in a resource group
resource resourceGroup 'Microsoft.Resources/resourceGroups@2021-04-01' = {
name: !empty(resourceGroupName) ? resourceGroupName : '${abbrs.resourcesResourceGroups}${environmentName}'
@@ -366,6 +374,7 @@ module function './app/function.bicep' = {
AZURE_FORMRECOGNIZER_SERVICE_ENDPOINT: formRecognizer.outputs.endpoint
AZURE_SEARCH_SERVICE_ENDPOINT: searchService.outputs.endpoint
AZURE_SEARCH_INDEX: searchIndexName
+ AZURE_SEARCH_SEMANTIC_RANKER: actualSearchServiceSemanticRankerLevel
AZURE_STORAGE_BLOB_ENDPOINT: storage.outputs.primaryEndpoints.blob
AZURE_OPENAI_EMBEDDING_DEPLOYMENT: useAOAI ? azureEmbeddingDeploymentName : ''
OPENAI_EMBEDDING_DEPLOYMENT: useAOAI ? '' : openAiEmbeddingDeployment
@@ -388,7 +397,7 @@ module monitoring 'core/monitor/monitoring.bicep' = {
includeApplicationInsights: true
logAnalyticsName: !empty(logAnalyticsName) ? logAnalyticsName : '${abbrs.operationalInsightsWorkspaces}${resourceToken}'
applicationInsightsName: !empty(applicationInsightsName) ? applicationInsightsName : '${abbrs.insightsComponents}${resourceToken}'
- applicationInsightsDashboardName: !empty(applicationInsightsDashboardName) ? applicationInsightsDashboardName : '${abbrs.portalDashboards}${resourceToken}'
+ applicationInsightsDashboardName: !empty(applicationInsightsDashboardName) ? applicationInsightsDashboardName : '${abbrs.portalDashboards}${resourceToken}'
}
}
@@ -490,7 +499,7 @@ module searchService 'core/search/search-services.bicep' = {
sku: {
name: searchServiceSkuName
}
- semanticSearch: 'free'
+ semanticSearch: actualSearchServiceSemanticRankerLevel //semanticSearch: 'free'
}
}
@@ -733,6 +742,7 @@ module visionRoleBackend 'core/security/role.bicep' = if (useVision) {
output APPLICATIONINSIGHTS_CONNECTION_STRING string = monitoring.outputs.applicationInsightsConnectionString
output APPLICATIONINSIGHTS_NAME string = monitoring.outputs.applicationInsightsName
+output AZURE_USE_APPLICATION_INSIGHTS bool = useApplicationInsights
output AZURE_CONTAINER_ENVIRONMENT_NAME string = containerApps.outputs.environmentName
output AZURE_CONTAINER_REGISTRY_ENDPOINT string = containerApps.outputs.registryLoginServer
output AZURE_CONTAINER_REGISTRY_NAME string = containerApps.outputs.registryName
@@ -758,6 +768,7 @@ output AZURE_SEARCH_INDEX string = searchIndexName
output AZURE_SEARCH_SERVICE string = searchService.outputs.name
output AZURE_SEARCH_SERVICE_ENDPOINT string = searchService.outputs.endpoint
output AZURE_SEARCH_SERVICE_RESOURCE_GROUP string = searchServiceResourceGroup.name
+output AZURE_SEARCH_SERVICE_SKU string = searchServiceSkuName
output AZURE_STORAGE_ACCOUNT string = storage.outputs.name
output AZURE_STORAGE_BLOB_ENDPOINT string = storage.outputs.primaryEndpoints.blob
output AZURE_STORAGE_CONTAINER string = storageContainerName
diff --git a/infra/main.parameters.json b/infra/main.parameters.json
index bd0f437f..bdfbdebb 100644
--- a/infra/main.parameters.json
+++ b/infra/main.parameters.json
@@ -18,7 +18,7 @@
"value": "${AZURE_FORMRECOGNIZER_SERVICE}"
},
"formRecognizerSkuName": {
- "value": "S0"
+ "value": "${AZURE_FORMRECOGNIZER_SERVICE_SKU=S0}"
},
"keyVaultName": {
"value": "${AZURE_KEY_VAULT_NAME}"
@@ -60,7 +60,10 @@
"value": "${AZURE_SEARCH_SERVICE_RESOURCE_GROUP}"
},
"searchServiceSkuName": {
- "value": "standard"
+ "value": "${AZURE_SEARCH_SERVICE_SKU=standard}"
+ },
+ "searchServiceSemanticRankerLevel": {
+ "value": "${AZURE_SEARCH_SEMANTIC_RANKER=free}"
},
"storageAccountName": {
"value": "${AZURE_STORAGE_ACCOUNT}"
@@ -77,6 +80,9 @@
"useApplicationInsights": {
"value": "${AZURE_USE_APPLICATION_INSIGHTS=true}"
},
+ "publicNetworkAccess": {
+ "value": "${AZURE_PUBLIC_NETWORK_ACCESS=Enabled}"
+ },
"openAIApiKey": {
"value": "${OPENAI_API_KEY}"
},
diff --git a/scripts/azd-env-copy.sh b/scripts/azd-env-copy.sh
index 15f06bf3..100b08bf 100755
--- a/scripts/azd-env-copy.sh
+++ b/scripts/azd-env-copy.sh
@@ -19,7 +19,7 @@ if [ ! -f ".azure/$SOURCE_ENV_NAME/.env" ]; then
fi
# Define the list of environment variables to be used
-VAR_LIST="AZURE_OPENAI_SERVICE AZURE_OPENAI_RESOURCE_GROUP AZURE_FORMRECOGNIZER_SERVICE AZURE_FORMRECOGNIZER_RESOURCE_GROUP AZURE_SEARCH_SERVICE AZURE_SEARCH_SERVICE_RESOURCE_GROUP AZURE_STORAGE_ACCOUNT AZURE_STORAGE_RESOURCE_GROUP AZURE_KEY_VAULT_NAME AZURE_KEY_VAULT_RESOURCE_GROUP SERVICE_WEB_IDENTITY_NAME"
+VAR_LIST="AZURE_OPENAI_SERVICE AZURE_OPENAI_RESOURCE_GROUP AZURE_FORMRECOGNIZER_SERVICE AZURE_FORMRECOGNIZER_RESOURCE_GROUP AZURE_SEARCH_SERVICE AZURE_SEARCH_SERVICE_RESOURCE_GROUP AZURE_SEARCH_SERVICE_SKU AZURE_STORAGE_ACCOUNT AZURE_STORAGE_RESOURCE_GROUP AZURE_KEY_VAULT_NAME AZURE_KEY_VAULT_RESOURCE_GROUP SERVICE_WEB_IDENTITY_NAME AZURE_APP_SERVICE_SKU"
echo "Variables to copy: $VAR_LIST"
diff --git a/scripts/azd-gh-vars.sh b/scripts/azd-gh-vars.sh
index 798160ac..4a4f1171 100755
--- a/scripts/azd-gh-vars.sh
+++ b/scripts/azd-gh-vars.sh
@@ -21,7 +21,7 @@ if [ ! -f ".azure/$AZD_ENV_NAME/.env" ]; then
fi
# Define the list of environment variables to be used
-VAR_LIST="AZURE_OPENAI_SERVICE AZURE_OPENAI_RESOURCE_GROUP AZURE_FORMRECOGNIZER_SERVICE AZURE_FORMRECOGNIZER_RESOURCE_GROUP AZURE_SEARCH_SERVICE AZURE_SEARCH_SERVICE_RESOURCE_GROUP AZURE_STORAGE_ACCOUNT AZURE_STORAGE_RESOURCE_GROUP AZURE_KEY_VAULT_NAME AZURE_KEY_VAULT_RESOURCE_GROUP SERVICE_WEB_IDENTITY_NAME"
+VAR_LIST="AZURE_OPENAI_SERVICE AZURE_OPENAI_RESOURCE_GROUP AZURE_FORMRECOGNIZER_SERVICE AZURE_FORMRECOGNIZER_RESOURCE_GROUP AZURE_SEARCH_SERVICE AZURE_SEARCH_SERVICE_RESOURCE_GROUP AZURE_SEARCH_SERVICE_SKU AZURE_STORAGE_ACCOUNT AZURE_STORAGE_RESOURCE_GROUP AZURE_KEY_VAULT_NAME AZURE_KEY_VAULT_RESOURCE_GROUP SERVICE_WEB_IDENTITY_NAME AZURE_APP_SERVICE_SKU"
echo "Variables to copy: $VAR_LIST"