Skip to content

Commit d5e11f3

Browse files
authored
Add low cost deployment process and update Azure AI Document Intelligence references (#364)
## Purpose This pull request updates the references to Azure Form Recognizer to Azure AI Document Intelligence. It also includes changes to the deployment process to support low-cost deployment using services with free tiers. The documentation has been updated to provide a step-by-step guide for deploying with minimal costs. Additionally, a video link has been added to showcase the low-cost deployment process. ## Does this introduce a breaking change? <!-- Mark one with an "x". --> ``` [ ] Yes [X] No ``` ## Pull Request Type What kind of change does this Pull Request introduce? <!-- Please check the one that applies to this PR using "x". --> ``` [ ] Bugfix [X] Feature [ ] Code style update (formatting, local variables) [X] Refactoring (no functional changes, no api changes) [X] Documentation content changes [ ] Other... Please describe: ``` ## How to Test * Get the code ``` git clone [repo-address] cd [repo-name] ``` Follow the low-cost deploy document instructions.
1 parent 1fc0647 commit d5e11f3

File tree

10 files changed

+103
-18
lines changed

10 files changed

+103
-18
lines changed

.github/workflows/azure-dev.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,14 @@ env:
2323
AZURE_RESOURCE_GROUP: ${{ vars.AZURE_RESOURCE_GROUP }}
2424
AZURE_DEV_USER_AGENT: ${{ secrets.AZURE_DEV_USER_AGENT }}
2525
# Existing resources, when applicable
26+
AZURE_APP_SERVICE_SKU: ${{ vars.azureAppServicePlanSku }}
2627
AZURE_OPENAI_SERVICE: ${{ vars.AZURE_OPENAI_SERVICE }}
2728
AZURE_OPENAI_RESOURCE_GROUP: ${{ vars.AZURE_OPENAI_RESOURCE_GROUP }}
2829
AZURE_FORMRECOGNIZER_SERVICE: ${{ vars.AZURE_FORMRECOGNIZER_SERVICE }}
2930
AZURE_FORMRECOGNIZER_RESOURCE_GROUP: ${{ vars.AZURE_FORMRECOGNIZER_RESOURCE_GROUP }}
3031
AZURE_SEARCH_SERVICE: ${{ vars.AZURE_SEARCH_SERVICE }}
3132
AZURE_SEARCH_SERVICE_RESOURCE_GROUP: ${{ vars.AZURE_SEARCH_SERVICE_RESOURCE_GROUP }}
33+
AZURE_SEARCH_SERVICE_SKU: ${{ vars.AZURE_SEARCH_SERVICE_SKU }}
3234
AZURE_STORAGE_ACCOUNT: ${{ vars.AZURE_STORAGE_ACCOUNT }}
3335
AZURE_STORAGE_RESOURCE_GROUP: ${{ vars.AZURE_STORAGE_RESOURCE_GROUP }}
3436
AZURE_KEY_VAULT_NAME: ${{ vars.AZURE_KEY_VAULT_NAME }}

README.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ In order to deploy and run this example, you'll need
9797
- **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).
9898

9999
> [!WARNING]<br>
100-
> 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.)
100+
> 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.)
101101
102102
### Project setup
103103

@@ -358,11 +358,13 @@ Pricing varies per region and usage, so it isn't possible to predict exact costs
358358

359359
- [**Azure Container Apps**](https://azure.microsoft.com/pricing/details/container-apps/)
360360
- [**Azure OpenAI Service**](https://azure.microsoft.com/pricing/details/cognitive-services/openai-service/)
361-
- [**Azure Form Recognizer**](https://azure.microsoft.com/pricing/details/form-recognizer/)
361+
- [**Azure AI Document Intelligence**](https://azure.microsoft.com/pricing/details/ai-document-intelligence/)
362362
- [**Azure AI Search**](https://azure.microsoft.com/pricing/details/search/)
363363
- [**Azure Blob Storage**](https://azure.microsoft.com/pricing/details/storage/blobs/)
364364
- [**Azure Monitor**](https://azure.microsoft.com/pricing/details/monitor/)
365365

366+
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.
367+
366368
## Resources
367369

368370
- [Revolutionize your Enterprise Data with ChatGPT: Next-gen Apps w/ Azure OpenAI and Azure AI Search](https://aka.ms/entgptsearchblog)

app/prepdocs/PrepareDocs/Program.Options.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ internal static partial class Program
3939
new(name: "--removeall", description: "Remove all blobs from blob storage and documents from the search index");
4040

4141
private static readonly Option<string> s_formRecognizerServiceEndpoint =
42-
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)");
42+
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)");
4343

4444
private static readonly Option<string> s_computerVisionServiceEndpoint =
4545
new(name: "--computervisionendpoint", description: "Optional. The Azure Computer Vision service endpoint which will be used to vectorize image and query");

app/shared/Shared/Services/AzureSearchEmbedService.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ public async Task EnsureSearchIndexAsync(string searchIndexName, CancellationTok
194194
public async Task<IReadOnlyList<PageDetail>> GetDocumentTextAsync(Stream blobStream, string blobName)
195195
{
196196
logger?.LogInformation(
197-
"Extracting text from '{Blob}' using Azure Form Recognizer", blobName);
197+
"Extracting text from '{Blob}' using Azure AI Document Intelligence", blobName);
198198

199199
using var ms = new MemoryStream();
200200
blobStream.CopyTo(ms);

docs/deploy_lowcost.md

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
# Deploying with Minimal Costs
2+
3+
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.
4+
5+
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).
6+
7+
[📺 Live stream: Deploying from a free account](https://youtu.be/V1ZLzXU4iiw)
8+
9+
1. Log in to your Azure account using the Azure Developer CLI:
10+
11+
```shell
12+
azd auth login
13+
```
14+
15+
1. Create a new azd environment for the free resource group:
16+
17+
```shell
18+
azd env new
19+
```
20+
21+
Enter a name that will be used for the resource group.
22+
This will create a new folder in the `.azure` folder, and set it as the active environment for any calls to `azd` going forward.
23+
24+
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)):
25+
26+
```shell
27+
azd env set AZURE_FORMRECOGNIZER_SERVICE_SKU F0
28+
```
29+
30+
1. Use the free tier of **Azure AI Search**:
31+
32+
```shell
33+
azd env set AZURE_SEARCH_SERVICE_SKU free
34+
azd env set AZURE_SEARCH_SEMANTIC_RANKER disabled
35+
```
36+
37+
Limitations:
38+
1. You are only allowed one free search service across all regions.
39+
If you have one already, either delete that service or follow instructions to
40+
reuse your [existing search service](../README.md#use-existing-resources).
41+
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).
42+
43+
1. Turn off **Azure Monitor** (Application Insights):
44+
45+
```shell
46+
azd env set AZURE_USE_APPLICATION_INSIGHTS false
47+
```
48+
49+
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.
50+
51+
1. (Optional) Use **OpenAI.com** instead of Azure OpenAI.
52+
53+
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.
54+
55+
```shell
56+
azd env set USE_AOAI false
57+
azd env set USE_VISION false
58+
azd env set OPENAI_CHATGPT_DEPLOYMENT gpt-4o-mini
59+
azd env set OPENAI_API_KEY <your openai.com key goes here>
60+
```
61+
62+
***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).*
63+
64+
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.

infra/app/web.bicep

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ param searchServiceEndpoint string
4141
@description('The search index name')
4242
param searchIndexName string
4343

44-
@description('The Form Recognizer endpoint')
44+
@description('The Azure AI Document Intelligence endpoint')
4545
param formRecognizerEndpoint string
4646

4747
@description('The Computer Vision endpoint')

infra/main.bicep

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,10 @@ param azureOpenAIChatGptModelName string = 'gpt-4o-mini'
2727
@allowed([ '0613', '2024-07-18' ])
2828
param azureOpenAIChatGptModelVersion string ='2024-07-18'
2929

30-
@description('Name of the Azure Application Insights dashboard')
30+
@description('Defines if the process will deploy an Azure Application Insights resource')
31+
param useApplicationInsights bool = true
32+
33+
// @description('Name of the Azure Application Insights dashboard')
3134
param applicationInsightsDashboardName string = ''
3235

3336
@description('Name of the Azure Application Insights resource')
@@ -72,16 +75,17 @@ param containerRegistryName string = ''
7275
@description('Name of the resource group for the Azure container registry')
7376
param containerRegistryResourceGroupName string = ''
7477

75-
@description('Location of the resource group for the Form Recognizer service')
78+
@description('Location of the resource group for the Azure AI Document Intelligence service')
7679
param formRecognizerResourceGroupLocation string = location
7780

78-
@description('Name of the resource group for the Form Recognizer service')
81+
@description('Name of the resource group for the Azure AI Document Intelligence service')
7982
param formRecognizerResourceGroupName string = ''
8083

81-
@description('Name of the Form Recognizer service')
84+
@description('Name of the Azure AI Document Intelligence service')
8285
param formRecognizerServiceName string = ''
8386

84-
@description('SKU name for the Form Recognizer service. Default: S0')
87+
@description('SKU name for the Azure AI Document Intelligence service. Default: S0')
88+
@allowed([ 'S0', 'F0' ])
8589
param formRecognizerSkuName string = 'S0'
8690

8791
@description('Name of the Azure Function App')
@@ -129,9 +133,14 @@ param searchServiceResourceGroupLocation string = location
129133
@description('Name of the resource group for the Azure AI Search service')
130134
param searchServiceResourceGroupName string = ''
131135

136+
@description('Azure AI Search Semantic Ranker Level')
137+
param searchServiceSemanticRankerLevel string // Set in main.parameters.json
138+
132139
@description('SKU name for the Azure AI Search service. Default: standard')
133140
param searchServiceSkuName string = 'standard'
134141

142+
var actualSearchServiceSemanticRankerLevel = (searchServiceSkuName == 'free') ? 'disabled' : searchServiceSemanticRankerLevel
143+
135144
@description('Name of the storage account')
136145
param storageAccountName string = ''
137146

@@ -168,7 +177,7 @@ param openAiChatGptDeployment string
168177
@description('OpenAI Embedding Model')
169178
param openAiEmbeddingDeployment string
170179

171-
@description('Use Vision retrival. default: false')
180+
@description('Use Vision retrieval. default: false')
172181
param useVision bool = false
173182

174183
var abbrs = loadJsonContent('./abbreviations.json')
@@ -177,7 +186,6 @@ var resourceToken = toLower(uniqueString(subscription().id, environmentName, loc
177186
var baseTags = { 'azd-env-name': environmentName }
178187
var updatedTags = union(empty(tags) ? {} : base64ToJson(tags), baseTags)
179188

180-
181189
// Organize resources in a resource group
182190
resource resourceGroup 'Microsoft.Resources/resourceGroups@2021-04-01' = {
183191
name: !empty(resourceGroupName) ? resourceGroupName : '${abbrs.resourcesResourceGroups}${environmentName}'
@@ -366,6 +374,7 @@ module function './app/function.bicep' = {
366374
AZURE_FORMRECOGNIZER_SERVICE_ENDPOINT: formRecognizer.outputs.endpoint
367375
AZURE_SEARCH_SERVICE_ENDPOINT: searchService.outputs.endpoint
368376
AZURE_SEARCH_INDEX: searchIndexName
377+
AZURE_SEARCH_SEMANTIC_RANKER: actualSearchServiceSemanticRankerLevel
369378
AZURE_STORAGE_BLOB_ENDPOINT: storage.outputs.primaryEndpoints.blob
370379
AZURE_OPENAI_EMBEDDING_DEPLOYMENT: useAOAI ? azureEmbeddingDeploymentName : ''
371380
OPENAI_EMBEDDING_DEPLOYMENT: useAOAI ? '' : openAiEmbeddingDeployment
@@ -388,7 +397,7 @@ module monitoring 'core/monitor/monitoring.bicep' = {
388397
includeApplicationInsights: true
389398
logAnalyticsName: !empty(logAnalyticsName) ? logAnalyticsName : '${abbrs.operationalInsightsWorkspaces}${resourceToken}'
390399
applicationInsightsName: !empty(applicationInsightsName) ? applicationInsightsName : '${abbrs.insightsComponents}${resourceToken}'
391-
applicationInsightsDashboardName: !empty(applicationInsightsDashboardName) ? applicationInsightsDashboardName : '${abbrs.portalDashboards}${resourceToken}'
400+
applicationInsightsDashboardName: !empty(applicationInsightsDashboardName) ? applicationInsightsDashboardName : '${abbrs.portalDashboards}${resourceToken}'
392401
}
393402
}
394403

@@ -490,7 +499,7 @@ module searchService 'core/search/search-services.bicep' = {
490499
sku: {
491500
name: searchServiceSkuName
492501
}
493-
semanticSearch: 'free'
502+
semanticSearch: actualSearchServiceSemanticRankerLevel //semanticSearch: 'free'
494503
}
495504
}
496505

@@ -733,6 +742,7 @@ module visionRoleBackend 'core/security/role.bicep' = if (useVision) {
733742

734743
output APPLICATIONINSIGHTS_CONNECTION_STRING string = monitoring.outputs.applicationInsightsConnectionString
735744
output APPLICATIONINSIGHTS_NAME string = monitoring.outputs.applicationInsightsName
745+
output AZURE_USE_APPLICATION_INSIGHTS bool = useApplicationInsights
736746
output AZURE_CONTAINER_ENVIRONMENT_NAME string = containerApps.outputs.environmentName
737747
output AZURE_CONTAINER_REGISTRY_ENDPOINT string = containerApps.outputs.registryLoginServer
738748
output AZURE_CONTAINER_REGISTRY_NAME string = containerApps.outputs.registryName
@@ -758,6 +768,7 @@ output AZURE_SEARCH_INDEX string = searchIndexName
758768
output AZURE_SEARCH_SERVICE string = searchService.outputs.name
759769
output AZURE_SEARCH_SERVICE_ENDPOINT string = searchService.outputs.endpoint
760770
output AZURE_SEARCH_SERVICE_RESOURCE_GROUP string = searchServiceResourceGroup.name
771+
output AZURE_SEARCH_SERVICE_SKU string = searchServiceSkuName
761772
output AZURE_STORAGE_ACCOUNT string = storage.outputs.name
762773
output AZURE_STORAGE_BLOB_ENDPOINT string = storage.outputs.primaryEndpoints.blob
763774
output AZURE_STORAGE_CONTAINER string = storageContainerName

infra/main.parameters.json

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
"value": "${AZURE_FORMRECOGNIZER_SERVICE}"
1919
},
2020
"formRecognizerSkuName": {
21-
"value": "S0"
21+
"value": "${AZURE_FORMRECOGNIZER_SERVICE_SKU=S0}"
2222
},
2323
"keyVaultName": {
2424
"value": "${AZURE_KEY_VAULT_NAME}"
@@ -60,7 +60,10 @@
6060
"value": "${AZURE_SEARCH_SERVICE_RESOURCE_GROUP}"
6161
},
6262
"searchServiceSkuName": {
63-
"value": "standard"
63+
"value": "${AZURE_SEARCH_SERVICE_SKU=standard}"
64+
},
65+
"searchServiceSemanticRankerLevel": {
66+
"value": "${AZURE_SEARCH_SEMANTIC_RANKER=free}"
6467
},
6568
"storageAccountName": {
6669
"value": "${AZURE_STORAGE_ACCOUNT}"
@@ -77,6 +80,9 @@
7780
"useApplicationInsights": {
7881
"value": "${AZURE_USE_APPLICATION_INSIGHTS=true}"
7982
},
83+
"publicNetworkAccess": {
84+
"value": "${AZURE_PUBLIC_NETWORK_ACCESS=Enabled}"
85+
},
8086
"openAIApiKey": {
8187
"value": "${OPENAI_API_KEY}"
8288
},

scripts/azd-env-copy.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ if [ ! -f ".azure/$SOURCE_ENV_NAME/.env" ]; then
1919
fi
2020

2121
# Define the list of environment variables to be used
22-
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"
22+
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"
2323

2424
echo "Variables to copy: $VAR_LIST"
2525

scripts/azd-gh-vars.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ if [ ! -f ".azure/$AZD_ENV_NAME/.env" ]; then
2121
fi
2222

2323
# Define the list of environment variables to be used
24-
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"
24+
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"
2525

2626
echo "Variables to copy: $VAR_LIST"
2727

0 commit comments

Comments
 (0)