diff --git a/labs/function-calling/function-calling.ipynb b/labs/function-calling/function-calling.ipynb
index c9624c2..629776c 100644
--- a/labs/function-calling/function-calling.ipynb
+++ b/labs/function-calling/function-calling.ipynb
@@ -19,16 +19,6 @@
"### Result\n",
"\n",
"\n",
- "### TOC\n",
- "- [0️⃣ Initialize notebook variables](#0)\n",
- "- [1️⃣ Create the Azure Resource Group](#1)\n",
- "- [2️⃣ Create deployment using 🦾 Bicep](#2)\n",
- "- [3️⃣ Get the deployment outputs](#3)\n",
- "- [4️⃣ Deploy the function](#4)\n",
- "- [🧪 Test the Function API](#function)\n",
- "- [🧪 Test OpenAI function calling](#functioncalling)\n",
- "- [🗑️ Clean up resources](#clean)\n",
- "\n",
"### Prerequisites\n",
"- [Python 3.12 or later version](https://www.python.org/) installed\n",
"- [Pandas Library](https://pandas.pydata.org/) installed\n",
@@ -61,33 +51,37 @@
},
"outputs": [],
"source": [
- "import os\n",
- "import json\n",
- "import datetime\n",
- "import requests\n",
+ "import os, sys, json\n",
+ "sys.path.insert(1, '../../shared') # add the shared directory to the Python path\n",
+ "import utils\n",
"\n",
"deployment_name = os.path.basename(os.path.dirname(globals()['__vsc_ipynb_file__']))\n",
"resource_group_name = f\"lab-{deployment_name}\" # change the name to match your naming style\n",
"resource_group_location = \"westeurope\"\n",
+ "\n",
"apim_resource_name = \"apim\"\n",
"apim_resource_location = \"westeurope\"\n",
"apim_resource_sku = \"Basicv2\"\n",
"openai_resources = [ {\"name\": \"openai1\", \"location\": \"swedencentral\"} ] # list of OpenAI resources to deploy. Clear this list to use only the mock resources\n",
"openai_resources_sku = \"S0\"\n",
- "openai_model_name = \"gpt-35-turbo\"\n",
- "openai_model_version = \"1106\"\n",
- "openai_deployment_name = \"gpt-35-turbo\"\n",
- "openai_api_version = \"2024-03-01-preview\"\n",
+ "\n",
+ "openai_model_name = \"gpt-4o-mini\"\n",
+ "openai_model_version = \"2024-07-18\"\n",
+ "openai_model_sku = \"GlobalStandard\"\n",
+ "openai_model_capacity = 20\n",
+ "openai_deployment_name = \"gpt-4o-mini\"\n",
+ "openai_api_version = \"2024-10-21\"\n",
+ "\n",
"openai_specification_url='https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cognitiveservices/data-plane/AzureOpenAI/inference/stable/2024-02-01/inference.json'\n",
"openai_backend_pool = \"openai-backend-pool\"\n",
- "mock_backend_pool = \"mock-backend-pool\"\n",
- "mock_webapps = [ ]\n",
"\n",
"log_analytics_name = \"workspace\"\n",
"app_insights_name = 'insights'\n",
"\n",
"function_app_name = \"function\"\n",
- "storage_account_name = \"storage\"\n"
+ "storage_account_name = \"storage\"\n",
+ "\n",
+ "utils.print_ok('Notebook initialized')"
]
},
{
@@ -95,8 +89,9 @@
"metadata": {},
"source": [
"\n",
- "### 1️⃣ Create the Azure Resource Group\n",
- "All resources deployed in this lab will be created in the specified resource group. Skip this step if you want to use an existing resource group."
+ "### 1️⃣ Verify the Azure CLI and the connected Azure subscription\n",
+ "\n",
+ "The following commands ensure that you have the latest version of the Azure CLI and that the Azure CLI is connected to your Azure subscription."
]
},
{
@@ -105,11 +100,16 @@
"metadata": {},
"outputs": [],
"source": [
- "resource_group_stdout = ! az group create --name {resource_group_name} --location {resource_group_location}\n",
- "if resource_group_stdout.n.startswith(\"ERROR\"):\n",
- " print(resource_group_stdout)\n",
- "else:\n",
- " print(\"✅ Azure Resource Group \", resource_group_name, \" created ⌚ \", datetime.datetime.now().time())"
+ "output = utils.run(\"az account show\", \"Retrieved az account\", \"Failed to get the current az account\")\n",
+ "\n",
+ "if output.success and output.json_data:\n",
+ " current_user = output.json_data['user']['name']\n",
+ " tenant_id = output.json_data['tenantId']\n",
+ " subscription_id = output.json_data['id']\n",
+ "\n",
+ " utils.print_info(f\"Current user: {current_user}\")\n",
+ " utils.print_info(f\"Tenant ID: {tenant_id}\")\n",
+ " utils.print_info(f\"Subscription ID: {subscription_id}\")"
]
},
{
@@ -119,7 +119,7 @@
"\n",
"### 2️⃣ Create deployment using 🦾 Bicep\n",
"\n",
- "This lab uses [Bicep](https://learn.microsoft.com/azure/azure-resource-manager/bicep/overview?tabs=bicep) to declarative define all the resources that will be deployed. Change the parameters or the [main.bicep](main.bicep) directly to try different configurations. "
+ "This lab uses [Bicep](https://learn.microsoft.com/azure/azure-resource-manager/bicep/overview?tabs=bicep) to declarative define all the resources that will be deployed in the specified resource group. Change the parameters or the [main.bicep](main.bicep) directly to try different configurations.\n"
]
},
{
@@ -128,29 +128,21 @@
"metadata": {},
"outputs": [],
"source": [
- "if len(openai_resources) > 0:\n",
- " backend_id = openai_backend_pool if len(openai_resources) > 1 else openai_resources[0].get(\"name\")\n",
- "elif len(mock_webapps) > 0:\n",
- " backend_id = mock_backend_pool if len(mock_backend_pool) > 1 else mock_webapps[0].get(\"name\")\n",
- "\n",
- "with open(\"policy.xml\", 'r') as policy_xml_file:\n",
- " policy_template_xml = policy_xml_file.read()\n",
- " policy_xml = policy_template_xml.replace(\"{backend-id}\", backend_id)\n",
- " policy_xml_file.close()\n",
- "open(\"policy.xml\", 'w').write(policy_xml)\n",
+ "# Create the resource group if doesn't exist\n",
+ "utils.create_resource_group(resource_group_name, resource_group_location)\n",
"\n",
+ "# Define the Bicep parameters\n",
"bicep_parameters = {\n",
" \"$schema\": \"https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#\",\n",
" \"contentVersion\": \"1.0.0.0\",\n",
" \"parameters\": {\n",
- " \"mockWebApps\": { \"value\": mock_webapps },\n",
- " \"mockBackendPoolName\": { \"value\": mock_backend_pool },\n",
" \"openAIBackendPoolName\": { \"value\": openai_backend_pool },\n",
" \"openAIConfig\": { \"value\": openai_resources },\n",
" \"openAIDeploymentName\": { \"value\": openai_deployment_name },\n",
" \"openAISku\": { \"value\": openai_resources_sku },\n",
" \"openAIModelName\": { \"value\": openai_model_name },\n",
" \"openAIModelVersion\": { \"value\": openai_model_version },\n",
+ " \"openAIModelSKU\": { \"value\": openai_model_sku },\n",
" \"openAIAPISpecURL\": { \"value\": openai_specification_url },\n",
" \"apimResourceName\": { \"value\": apim_resource_name},\n",
" \"apimResourceLocation\": { \"value\": apim_resource_location},\n",
@@ -161,12 +153,14 @@
" \"storageAccountName\": { \"value\": storage_account_name }\n",
" }\n",
"}\n",
+ "\n",
+ "# Write the parameters to the params.json file\n",
"with open('params.json', 'w') as bicep_parameters_file:\n",
" bicep_parameters_file.write(json.dumps(bicep_parameters))\n",
"\n",
- "! az deployment group create --name {deployment_name} --resource-group {resource_group_name} --template-file \"main.bicep\" --parameters \"params.json\"\n",
- "\n",
- "open(\"policy.xml\", 'w').write(policy_template_xml)\n"
+ "# Run the deployment\n",
+ "output = utils.run(f\"az deployment group create --name {deployment_name} --resource-group {resource_group_name} --template-file main.bicep --parameters params.json\",\n",
+ " f\"Deployment '{deployment_name}' succeeded\", f\"Deployment '{deployment_name}' failed\")"
]
},
{
@@ -174,7 +168,9 @@
"metadata": {},
"source": [
"\n",
- "### 3️⃣ Get the deployment outputs\n"
+ "### 3️⃣ Get the deployment outputs\n",
+ "\n",
+ "Retrieve the required outputs from the Bicep deployment."
]
},
{
@@ -183,23 +179,13 @@
"metadata": {},
"outputs": [],
"source": [
- "deployment_stdout = ! az deployment group show --name {deployment_name} -g {resource_group_name} --query properties.outputs.apimSubscriptionKey.value -o tsv\n",
- "apim_subscription_key = deployment_stdout.n\n",
- "deployment_stdout = ! az deployment group show --name {deployment_name} -g {resource_group_name} --query properties.outputs.apimResourceGatewayURL.value -o tsv\n",
- "apim_resource_gateway_url = deployment_stdout.n\n",
- "print(\"👉🏻 API Gateway URL: \", apim_resource_gateway_url)\n",
- "\n",
- "deployment_stdout = ! az deployment group show --name {deployment_name} -g {resource_group_name} --query properties.outputs.logAnalyticsWorkspaceId.value -o tsv\n",
- "workspace_id = deployment_stdout.n\n",
- "print(\"👉🏻 Workspace ID: \", workspace_id)\n",
- "\n",
- "deployment_stdout = ! az deployment group show --name {deployment_name} -g {resource_group_name} --query properties.outputs.applicationInsightsAppId.value -o tsv\n",
- "app_id = deployment_stdout.n\n",
- "print(\"👉🏻 App ID: \", app_id)\n",
- "\n",
- "deployment_stdout = ! az deployment group show --name {deployment_name} -g {resource_group_name} --query properties.outputs.functionAppResourceName.value -o tsv\n",
- "function_app_resource_name = deployment_stdout.n\n",
- "print(\"👉🏻 Function Name: \", function_app_resource_name)\n"
+ "# Obtain all of the outputs from the deployment\n",
+ "output = utils.run(f\"az deployment group show --name {deployment_name} -g {resource_group_name}\", f\"Retrieved deployment: {deployment_name}\", f\"Failed to retrieve deployment: {deployment_name}\")\n",
+ "\n",
+ "if output.success and output.json_data:\n",
+ " apim_resource_gateway_url = utils.get_deployment_output(output, 'apimResourceGatewayURL', 'APIM API Gateway URL')\n",
+ " apim_subscription_key = utils.get_deployment_output(output, 'apimSubscriptionKey', 'APIM Subscription Key (masked)', True)\n",
+ " function_app_resource_name = utils.get_deployment_output(output, 'functionAppResourceName', 'Function App Resource Name')\n"
]
},
{
@@ -235,6 +221,8 @@
"metadata": {},
"outputs": [],
"source": [
+ "import requests\n",
+ "\n",
"request = { \"location\": \"London\", \"unit\": \"celsius\" }\n",
"url = apim_resource_gateway_url + \"/weather\"\n",
"response = requests.post(url, headers = {'api-key':apim_subscription_key}, json = request)\n",
@@ -265,6 +253,7 @@
"metadata": {},
"outputs": [],
"source": [
+ "# type: ignore\n",
"from openai import AzureOpenAI\n",
"import uuid\n",
"\n",
@@ -390,7 +379,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
- "version": "3.12.2"
+ "version": "3.12.9"
}
},
"nbformat": 4,
diff --git a/labs/function-calling/main.bicep b/labs/function-calling/main.bicep
index d8c56a8..58fbc59 100644
--- a/labs/function-calling/main.bicep
+++ b/labs/function-calling/main.bicep
@@ -1,11 +1,3 @@
-@description('List of Mock webapp names used to simulate OpenAI behavior.')
-param mockWebApps array = []
-
-@description('The name of the OpenAI mock backend pool')
-param mockBackendPoolName string = 'openai-backend-pool'
-
-@description('The description of the OpenAI mock backend pool')
-param mockBackendPoolDescription string = 'Load balancer for multiple OpenAI Mocking endpoints'
@description('List of OpenAI resources to create. Add pairs of name and location.')
param openAIConfig array = []
@@ -25,6 +17,9 @@ param openAIModelName string
@description('Model Version')
param openAIModelVersion string
+@description('Model SKU')
+param openAIModelSKU string
+
@description('Model Capacity')
param openAIModelCapacity int = 20
@@ -162,6 +157,10 @@ param functionAPIDescription string = 'Weather API'
var resourceSuffix = uniqueString(subscription().id, resourceGroup().id)
+var policyXml = loadTextContent('policy.xml')
+var updatedPolicyXml = replace(policyXml, '{backend-id}', (length(openAIConfig) > 1) ? 'openai-backend-pool' : openAIConfig[0].name)
+
+
resource cognitiveServices 'Microsoft.CognitiveServices/accounts@2021-10-01' = [for config in openAIConfig: if(length(openAIConfig) > 0) {
name: '${config.name}-${resourceSuffix}'
location: config.location
@@ -188,7 +187,7 @@ resource deployment 'Microsoft.CognitiveServices/accounts/deployments@2023-05-01
}
}
sku: {
- name: 'Standard'
+ name: openAIModelSKU
capacity: openAIModelCapacity
}
}]
@@ -247,7 +246,7 @@ resource apiPolicy 'Microsoft.ApiManagement/service/apis/policies@2021-12-01-pre
parent: api
properties: {
format: 'rawxml'
- value: loadTextContent('policy.xml')
+ value: updatedPolicyXml
}
}
@@ -282,45 +281,14 @@ resource backendOpenAI 'Microsoft.ApiManagement/service/backends@2023-05-01-prev
}
}]
-resource backendMock 'Microsoft.ApiManagement/service/backends@2023-05-01-preview' = [for (mock, i) in mockWebApps: if(length(openAIConfig) == 0 && length(mockWebApps) > 0) {
- name: mock.name
- parent: apimService
- properties: {
- description: 'backend description'
- url: '${mock.endpoint}/openai'
- protocol: 'http'
- circuitBreaker: {
- rules: [
- {
- failureCondition: {
- count: 3
- errorReasons: [
- 'Server errors'
- ]
- interval: 'PT5M'
- statusCodeRanges: [
- {
- min: 429
- max: 429
- }
- ]
- }
- name: 'mockBreakerRule'
- tripDuration: 'PT1M'
- }
- ]
- }
- }
-}]
-
resource backendPoolOpenAI 'Microsoft.ApiManagement/service/backends@2023-05-01-preview' = if(length(openAIConfig) > 1) {
name: openAIBackendPoolName
parent: apimService
+ // BCP035: protocol and url are not needed in the Pool type. This is an incorrect error.
+ #disable-next-line BCP035
properties: {
description: openAIBackendPoolDescription
type: 'Pool'
-// protocol: 'http' // the protocol is not needed in the Pool type
-// url: '${cognitiveServices[0].properties.endpoint}/openai' // the url is not needed in the Pool type
pool: {
services: [for (config, i) in openAIConfig: {
id: '/backends/${backendOpenAI[i].name}'
@@ -330,23 +298,6 @@ resource backendPoolOpenAI 'Microsoft.ApiManagement/service/backends@2023-05-01-
}
}
-resource backendPoolMock 'Microsoft.ApiManagement/service/backends@2023-05-01-preview' = if(length(openAIConfig) == 0 && length(mockWebApps) > 1) {
- name: mockBackendPoolName
- parent: apimService
- properties: {
- description: mockBackendPoolDescription
- type: 'Pool'
-// protocol: 'http' // the protocol is not needed in the Pool type
-// url: '${mockWebApps[0].endpoint}/openai' // the url is not needed in the Pool type
- pool: {
- services: [for (webApp, i) in mockWebApps: {
- id: '/backends/${backendMock[i].name}'
- }
- ]
- }
- }
-}
-
resource apimSubscription 'Microsoft.ApiManagement/service/subscriptions@2023-05-01-preview' = {
name: openAISubscriptionName
parent: apimService