Skip to content

Commit

Permalink
Add low cost deployment process and update Azure AI Document Intellig…
Browse files Browse the repository at this point in the history
…ence 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.
  • Loading branch information
elbruno authored Aug 30, 2024
1 parent 1fc0647 commit d5e11f3
Show file tree
Hide file tree
Showing 10 changed files with 103 additions and 18 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/azure-dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }}
Expand Down
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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]<br>
> 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

Expand Down Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion app/prepdocs/PrepareDocs/Program.Options.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<string> 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<string> s_computerVisionServiceEndpoint =
new(name: "--computervisionendpoint", description: "Optional. The Azure Computer Vision service endpoint which will be used to vectorize image and query");
Expand Down
2 changes: 1 addition & 1 deletion app/shared/Shared/Services/AzureSearchEmbedService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ public async Task EnsureSearchIndexAsync(string searchIndexName, CancellationTok
public async Task<IReadOnlyList<PageDetail>> 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);
Expand Down
64 changes: 64 additions & 0 deletions docs/deploy_lowcost.md
Original file line number Diff line number Diff line change
@@ -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 <your openai.com key goes here>
```

***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.
2 changes: 1 addition & 1 deletion infra/app/web.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down
29 changes: 20 additions & 9 deletions infra/main.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down Expand Up @@ -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')
Expand Down Expand Up @@ -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 = ''

Expand Down Expand Up @@ -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')
Expand All @@ -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}'
Expand Down Expand Up @@ -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
Expand All @@ -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}'
}
}

Expand Down Expand Up @@ -490,7 +499,7 @@ module searchService 'core/search/search-services.bicep' = {
sku: {
name: searchServiceSkuName
}
semanticSearch: 'free'
semanticSearch: actualSearchServiceSemanticRankerLevel //semanticSearch: 'free'
}
}

Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down
10 changes: 8 additions & 2 deletions infra/main.parameters.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
"value": "${AZURE_FORMRECOGNIZER_SERVICE}"
},
"formRecognizerSkuName": {
"value": "S0"
"value": "${AZURE_FORMRECOGNIZER_SERVICE_SKU=S0}"
},
"keyVaultName": {
"value": "${AZURE_KEY_VAULT_NAME}"
Expand Down Expand Up @@ -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}"
Expand All @@ -77,6 +80,9 @@
"useApplicationInsights": {
"value": "${AZURE_USE_APPLICATION_INSIGHTS=true}"
},
"publicNetworkAccess": {
"value": "${AZURE_PUBLIC_NETWORK_ACCESS=Enabled}"
},
"openAIApiKey": {
"value": "${OPENAI_API_KEY}"
},
Expand Down
2 changes: 1 addition & 1 deletion scripts/azd-env-copy.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down
2 changes: 1 addition & 1 deletion scripts/azd-gh-vars.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down

0 comments on commit d5e11f3

Please sign in to comment.