diff --git a/README.md b/README.md index 2e66c16c71..6ffc0d1424 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ The repo includes sample data so it's ready to try end to end. In this sample ap * Explores various options to help users evaluate the trustworthiness of responses with citations, tracking of source content, etc. * Shows possible approaches for data preparation, prompt construction, and orchestration of interaction between model (ChatGPT) and retriever (Cognitive Search) * Settings directly in the UX to tweak the behavior and experiment with options +* Optional performance tracing and monitoring with Application Insights ![Chat screen](docs/chatscreen.png) @@ -108,6 +109,20 @@ either you or they can follow these steps: 1. Set the environment variable `AZURE_PRINCIPAL_ID` either in that `.env` file or in the active shell to their Azure ID, which they can get with `az account show`. 1. Run `./scripts/roles.ps1` or `.scripts/roles.sh` to assign all of the necessary roles to the user. If they do not have the necessary permission to create roles in the subscription, then you may need to run this script for them. Once the script runs, they should be able to run the app locally. +#### Enabling Application Insights + +To enable Application Insights and the tracing of each request, along with the logging of errors, set the `AZURE_USE_APPLICATION_INSIGHTS` variable to true before running `azd up` + +1. Run `azd env set AZURE_USE_APPLICATION_INSIGHTS true` +1. Run `azd up` + +To see the performance data, go to the Application Insights resource in your resource group, click on the "Investigate -> Performance" blade and navigate to any HTTP request to see the timing data. +To inspect the performance of chat requests, use the "Drill into Samples" button to see end-to-end traces of all the API calls made for any chat request: + +![Tracing screenshot](docs/transaction-tracing.png) + +To see any exceptions and server errors, navigate to the "Investigate -> Failures" blade and use the filtering tools to locate a specific exception. You can see Python stack traces on the right-hand side. + ### Quickstart * In Azure: navigate to the Azure WebApp deployed by azd. The URL is printed out when azd completes (as "Endpoint"), or you can find it in the Azure portal. diff --git a/app/backend/app.py b/app/backend/app.py index 07fd61a4b9..45cf58e136 100644 --- a/app/backend/app.py +++ b/app/backend/app.py @@ -6,8 +6,11 @@ import openai from azure.identity.aio import DefaultAzureCredential +from azure.monitor.opentelemetry import configure_azure_monitor from azure.search.documents.aio import SearchClient from azure.storage.blob.aio import BlobServiceClient +from opentelemetry.instrumentation.aiohttp_client import AioHttpClientInstrumentor +from opentelemetry.instrumentation.asgi import OpenTelemetryMiddleware from quart import ( Blueprint, Quart, @@ -45,6 +48,7 @@ CONFIG_CHAT_APPROACHES = "chat_approaches" CONFIG_BLOB_CLIENT = "blob_client" +APPLICATIONINSIGHTS_CONNECTION_STRING = os.getenv("APPLICATIONINSIGHTS_CONNECTION_STRING") bp = Blueprint("routes", __name__, static_folder='static') @@ -123,7 +127,7 @@ async def setup_clients(): # Use the current user identity to authenticate with Azure OpenAI, Cognitive Search and Blob Storage (no secrets needed, # just use 'az login' locally, and managed identity when deployed on Azure). If you need to use keys, use separate AzureKeyCredential instances with the # keys for each service - # If you encounter a blocking error during a DefaultAzureCredntial resolution, you can exclude the problematic credential by using a parameter (ex. exclude_shared_token_cache_credential=True) + # If you encounter a blocking error during a DefaultAzureCredential resolution, you can exclude the problematic credential by using a parameter (ex. exclude_shared_token_cache_credential=True) azure_credential = DefaultAzureCredential(exclude_shared_token_cache_credential = True) # Set up clients for Cognitive Search and Storage @@ -186,6 +190,11 @@ async def setup_clients(): def create_app(): + if APPLICATIONINSIGHTS_CONNECTION_STRING: + configure_azure_monitor() + AioHttpClientInstrumentor().instrument() app = Quart(__name__) app.register_blueprint(bp) + app.asgi_app = OpenTelemetryMiddleware(app.asgi_app) + return app diff --git a/app/backend/requirements.txt b/app/backend/requirements.txt index a15d6a4533..4831ab80d7 100644 --- a/app/backend/requirements.txt +++ b/app/backend/requirements.txt @@ -6,3 +6,8 @@ tiktoken==0.4.0 azure-search-documents==11.4.0b6 azure-storage-blob==12.14.1 uvicorn[standard]==0.23.2 + +azure-monitor-opentelemetry==1.0.0b15 +opentelemetry-instrumentation-asgi==0.40b0 +opentelemetry-instrumentation-requests==0.40b0 +opentelemetry-instrumentation-aiohttp-client==0.40b0 diff --git a/docs/transaction-tracing.png b/docs/transaction-tracing.png new file mode 100644 index 0000000000..390ec35e5c Binary files /dev/null and b/docs/transaction-tracing.png differ diff --git a/infra/core/monitor/applicationinsights.bicep b/infra/core/monitor/applicationinsights.bicep new file mode 100644 index 0000000000..0d9bc4740f --- /dev/null +++ b/infra/core/monitor/applicationinsights.bicep @@ -0,0 +1,17 @@ +param name string +param location string = resourceGroup().location +param tags object = {} + +resource applicationInsights 'Microsoft.Insights/components@2020-02-02' = { + name: name + location: location + tags: tags + kind: 'web' + properties: { + Application_Type: 'web' + } +} + +output connectionString string = applicationInsights.properties.ConnectionString +output instrumentationKey string = applicationInsights.properties.InstrumentationKey +output name string = applicationInsights.name diff --git a/infra/core/monitor/monitoring.bicep b/infra/core/monitor/monitoring.bicep new file mode 100644 index 0000000000..0143363f46 --- /dev/null +++ b/infra/core/monitor/monitoring.bicep @@ -0,0 +1,17 @@ +param applicationInsightsName string +param location string = resourceGroup().location +param tags object = {} + +module applicationInsights 'applicationinsights.bicep' = { + name: 'applicationinsights' + params: { + name: applicationInsightsName + location: location + tags: tags + } +} + +output applicationInsightsConnectionString string = applicationInsights.outputs.connectionString +output applicationInsightsInstrumentationKey string = applicationInsights.outputs.instrumentationKey +output applicationInsightsName string = applicationInsights.outputs.name + diff --git a/infra/main.bicep b/infra/main.bicep index d9c8bb6150..d954bba08e 100644 --- a/infra/main.bicep +++ b/infra/main.bicep @@ -13,6 +13,8 @@ param appServicePlanName string = '' param backendServiceName string = '' param resourceGroupName string = '' +param applicationInsightsName string = '' + param searchServiceName string = '' param searchServiceResourceGroupName string = '' param searchServiceResourceGroupLocation string = location @@ -57,6 +59,9 @@ param embeddingModelName string = 'text-embedding-ada-002' @description('Id of the user or app to assign application roles') param principalId string = '' +@description('Use Application Insights for monitoring and performance tracing') +param useApplicationInsights bool = false + var abbrs = loadJsonContent('abbreviations.json') var resourceToken = toLower(uniqueString(subscription().id, environmentName, location)) var tags = { 'azd-env-name': environmentName } @@ -84,6 +89,17 @@ resource storageResourceGroup 'Microsoft.Resources/resourceGroups@2021-04-01' ex name: !empty(storageResourceGroupName) ? storageResourceGroupName : resourceGroup.name } +// Monitor application with Azure Monitor +module monitoring './core/monitor/monitoring.bicep' = if (useApplicationInsights) { + name: 'monitoring' + scope: resourceGroup + params: { + location: location + tags: tags + applicationInsightsName: !empty(applicationInsightsName) ? applicationInsightsName : '${abbrs.insightsComponents}${resourceToken}' + } +} + // Create an App Service Plan to group applications under the same payment plan and SKU module appServicePlan 'core/host/appserviceplan.bicep' = { name: 'appserviceplan' @@ -123,6 +139,7 @@ module backend 'core/host/appservice.bicep' = { AZURE_OPENAI_GPT_DEPLOYMENT: gptDeploymentName AZURE_OPENAI_CHATGPT_DEPLOYMENT: chatGptDeploymentName AZURE_OPENAI_EMB_DEPLOYMENT: embeddingDeploymentName + APPLICATIONINSIGHTS_CONNECTION_STRING: useApplicationInsights ? monitoring.outputs.applicationInsightsConnectionString : '' } } } diff --git a/infra/main.parameters.json b/infra/main.parameters.json index 7896dc5688..720dceb4a2 100644 --- a/infra/main.parameters.json +++ b/infra/main.parameters.json @@ -55,6 +55,9 @@ }, "gptDeploymentName": { "value": "${AZURE_OPENAI_GPT_DEPLOYMENT=davinci}" + }, + "useApplicationInsights": { + "value": "${AZURE_USE_APPLICATION_INSIGHTS=false}" } } }