infra/modules/apim/main.tf (205 lines of code) (raw):

locals { logger_name = "openai-appi-logger" backend_url = "${var.openai_service_endpoint}openai" } resource "azapi_resource" "apim" { type = "Microsoft.ApiManagement/service@2023-03-01-preview" name = var.apim_name parent_id = var.resource_group_id location = var.location identity { type = "SystemAssigned" } schema_validation_enabled = false # requiered for now body = { sku = { name = "StandardV2" capacity = 1 } properties = { publisherEmail = var.publisher_email publisherName = var.publisher_name apiVersionConstraint = {} developerPortalStatus = "Disabled" virtualNetworkType = var.use_private_endpoints ? "External" : "None" virtualNetworkConfiguration = var.use_private_endpoints ? { subnetResourceId = var.apim_subnet_id } : null } } response_export_values = [ "identity.principalId", "properties.gatewayUrl" ] } resource "azurerm_api_management_backend" "openai" { name = "openai-api" resource_group_name = var.resource_group_name api_management_name = azapi_resource.apim.name protocol = "http" url = local.backend_url tls { validate_certificate_chain = true validate_certificate_name = true } } resource "azapi_resource" "apim_backend_pool" { type = "Microsoft.ApiManagement/service/backends@2023-09-01-preview" parent_id = azapi_resource.apim.id name = "openai-backend-pool" schema_validation_enabled = false # requiered for now body = { properties = { description = "Azure OpenAI Backend Pool" type = "Pool" pool = { services = [ { id = azurerm_api_management_backend.openai.id priority = 1 weight = 1 } ] } } } } resource "azurerm_api_management_logger" "appi_logger" { name = local.logger_name api_management_name = azapi_resource.apim.name resource_group_name = var.resource_group_name resource_id = var.appi_resource_id application_insights { instrumentation_key = var.appi_instrumentation_key } } // https://learn.microsoft.com/en-us/semantic-kernel/deploy/use-ai-apis-with-api-management#setup-azure-api-management-instance-with-azure-openai-api resource "azurerm_api_management_api" "openai" { name = "openai-api" resource_group_name = var.resource_group_name api_management_name = azapi_resource.apim.name revision = "1" display_name = "Azure Open AI API" path = "openai" protocols = ["https"] subscription_required = false service_url = local.backend_url import { content_format = "openapi-link" content_value = "https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cognitiveservices/data-plane/AzureOpenAI/inference/preview/2023-10-01-preview/inference.json" } } resource "azurerm_api_management_named_value" "tenant_id" { name = "tenant-id" resource_group_name = var.resource_group_name api_management_name = azapi_resource.apim.name display_name = "TENANT_ID" value = var.tenant_id } resource "azurerm_api_management_api_policy" "policy" { api_name = azurerm_api_management_api.openai.name api_management_name = azapi_resource.apim.name resource_group_name = var.resource_group_name xml_content = <<XML <policies> <inbound> <base /> <validate-jwt header-name="Authorization" failed-validation-httpcode="403" failed-validation-error-message="Forbidden"> <openid-config url="https://login.microsoftonline.com/{{TENANT_ID}}/v2.0/.well-known/openid-configuration" /> <issuers> <issuer>https://sts.windows.net/{{TENANT_ID}}/</issuer> </issuers> <required-claims> <claim name="aud"> <value>https://cognitiveservices.azure.com</value> </claim> </required-claims> </validate-jwt> <choose> <when condition="@(context.Request.Body.As<JObject>(preserveContent: true)["messages"]?.All(message => message["content"].All(content => !(content is JObject))) == true)"> <!-- If all type properties are 'text' or there are no type properties, apply the new Azure OpenAI policies --> <trace source="Azure OpenAI Policies" severity="information"> <message>Using Azure OpenAI policies.</message> <metadata name="Using_Azure_OpenAI_Policies" value="true" /> </trace> <azure-openai-emit-token-metric namespace="AzureOpenAI"> <dimension name="API ID" /> <dimension name="Operation ID" /> <dimension name="Client IP" value="@(context.Request.IpAddress)" /> </azure-openai-emit-token-metric> <azure-openai-token-limit counter-key="@(context.Request.IpAddress)" tokens-per-minute="10000" estimate-prompt-tokens="false" remaining-tokens-variable-name="remainingTokens" /> </when> <otherwise> <trace source="Azure OpenAI Policies" severity="information"> <message>Not using Azure OpenAI policies.</message> <metadata name="Using_Azure_OpenAI_Policies" value="false" /> </trace> </otherwise> </choose> <set-backend-service backend-id="${azapi_resource.apim_backend_pool.name}" /> </inbound> <backend> <base /> </backend> <outbound> <base /> </outbound> <on-error> <base /> </on-error> </policies> XML depends_on = [azurerm_api_management_named_value.tenant_id] } # https://github.com/aavetis/azure-openai-logger/blob/main/README.md # KQL Query to extract OpenAI data from Application Insights # customMetrics # | extend ip = tostring(parse_json(customDimensions).["Client IP"]) # | summarize totalValueSum = sum(valueSum) by name, ip resource "azurerm_api_management_diagnostic" "diagnostics" { identifier = "applicationinsights" resource_group_name = var.resource_group_name api_management_name = azapi_resource.apim.name api_management_logger_id = azurerm_api_management_logger.appi_logger.id sampling_percentage = 100 always_log_errors = true log_client_ip = false verbosity = "information" http_correlation_protocol = "W3C" frontend_request { body_bytes = 8192 headers_to_log = [] data_masking { query_params { mode = "Hide" value = "*" } } } frontend_response { body_bytes = 8192 headers_to_log = [] } backend_request { body_bytes = 8192 headers_to_log = [] data_masking { query_params { mode = "Hide" value = "*" } } } backend_response { body_bytes = 8192 headers_to_log = [] } } # https://learn.microsoft.com/en-us/azure/api-management/api-management-howto-app-insights?tabs=rest#emit-custom-metrics resource "azapi_update_resource" "diagnostics" { type = "Microsoft.ApiManagement/service/diagnostics@2022-08-01" resource_id = azurerm_api_management_diagnostic.diagnostics.id body = { properties = { loggerId = azurerm_api_management_logger.appi_logger.id metrics = true } } }