Skip to content

Commit 4b83e18

Browse files
authored
Refactor to use the built-in LLM Logging and AI Foundry (#182)
* Refactor to use the built-in LLM Logging and AI Foundry
1 parent 83fbed6 commit 4b83e18

File tree

97 files changed

+22242
-7726
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

97 files changed

+22242
-7726
lines changed

labs/access-controlling/README.MD

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,18 @@
1-
# APIM ❤️ OpenAI
1+
# APIM ❤️ AI Foundry
22

33
## [Access Controlling lab](access-controlling.ipynb)
44

55
[![flow](../../images/access-controlling.gif)](access-controlling.ipynb)
66

7-
Playground to try the [OAuth 2.0 authorization feature](https://learn.microsoft.com/azure/api-management/api-management-authenticate-authorize-azure-openai#oauth-20-authorization-using-identity-provider) using identity provider to enable more fine-grained access to OpenAPI APIs by particular users or client.
7+
Playground to try the [OAuth 2.0 authorization feature](https://learn.microsoft.com/azure/api-management/api-management-authenticate-authorize-azure-openai#oauth-20-authorization-using-identity-provider) using identity provider to enable more fine-grained access to AI Foundry models by particular users or client.
88

99
### Prerequisites
1010

1111
- [Python 3.12 or later version](https://www.python.org/) installed
12-
- [Pandas Library](https://pandas.pydata.org) installed
1312
- [VS Code](https://code.visualstudio.com/) installed with the [Jupyter notebook extension](https://marketplace.visualstudio.com/items?itemName=ms-toolsai.jupyter) enabled
14-
- [Azure CLI](https://learn.microsoft.com/cli/azure/install-azure-cli) installed
15-
- [An Azure Subscription](https://azure.microsoft.com/free/) with Contributor permissions
16-
- [Access granted to Azure OpenAI](https://aka.ms/oai/access) or just enable the mock service
17-
- [Sign in to Azure with Azure CLI](https://learn.microsoft.com/cli/azure/authenticate-azure-cli-interactively)
13+
- [Python environment](https://code.visualstudio.com/docs/python/environments#_creating-environments) with the [requirements.txt](../../requirements.txt) or run `pip install -r requirements.txt` in your terminal
14+
- [An Azure Subscription](https://azure.microsoft.com/free/) with [Contributor](https://learn.microsoft.com/en-us/azure/role-based-access-control/built-in-roles/privileged#contributor) + [RBAC Administrator](https://learn.microsoft.com/en-us/azure/role-based-access-control/built-in-roles/privileged#role-based-access-control-administrator) or [Owner](https://learn.microsoft.com/en-us/azure/role-based-access-control/built-in-roles/privileged#owner) roles
15+
- [Azure CLI](https://learn.microsoft.com/cli/azure/install-azure-cli) installed and [Signed into your Azure subscription](https://learn.microsoft.com/cli/azure/authenticate-azure-cli-interactively)
1816

1917
### 🚀 Get started
2018

labs/access-controlling/access-controlling.ipynb

Lines changed: 50 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,22 @@
44
"cell_type": "markdown",
55
"metadata": {},
66
"source": [
7-
"# APIM ❤️ OpenAI\n",
7+
"# APIM ❤️ AI Foundry\n",
88
"\n",
99
"## Access Controlling lab\n",
1010
"![flow](../../images/access-controlling.gif)\n",
1111
"\n",
12-
"Playground to try the [OAuth 2.0 authorization feature](https://learn.microsoft.com/azure/api-management/api-management-authenticate-authorize-azure-openai#oauth-20-authorization-using-identity-provider) using identity provider to enable more fine-grained access to OpenAPI APIs by particular users or client.\n",
12+
"Playground to try the [OAuth 2.0 authorization feature](https://learn.microsoft.com/azure/api-management/api-management-authenticate-authorize-azure-openai#oauth-20-authorization-using-identity-provider) using identity provider to enable more fine-grained access to AI Foundry models by particular users or client.\n",
1313
"\n",
1414
"### Prerequisites\n",
15+
"\n",
1516
"- [Python 3.12 or later version](https://www.python.org/) installed\n",
16-
"- [Pandas Library](https://pandas.pydata.org/) installed\n",
1717
"- [VS Code](https://code.visualstudio.com/) installed with the [Jupyter notebook extension](https://marketplace.visualstudio.com/items?itemName=ms-toolsai.jupyter) enabled\n",
18-
"- [Azure CLI](https://learn.microsoft.com/cli/azure/install-azure-cli) installed\n",
19-
"- [An Azure Subscription](https://azure.microsoft.com/free/) with Contributor permissions\n",
20-
"- [Access granted to Azure OpenAI](https://aka.ms/oai/access) or just enable the mock service\n",
21-
"- [Sign in to Azure with Azure CLI](https://learn.microsoft.com/cli/azure/authenticate-azure-cli-interactively)"
18+
"- [Python environment](https://code.visualstudio.com/docs/python/environments#_creating-environments) with the [requirements.txt](../../requirements.txt) or run `pip install -r requirements.txt` in your terminal\n",
19+
"- [An Azure Subscription](https://azure.microsoft.com/free/) with [Contributor](https://learn.microsoft.com/en-us/azure/role-based-access-control/built-in-roles/privileged#contributor) + [RBAC Administrator](https://learn.microsoft.com/en-us/azure/role-based-access-control/built-in-roles/privileged#role-based-access-control-administrator) or [Owner](https://learn.microsoft.com/en-us/azure/role-based-access-control/built-in-roles/privileged#owner) roles\n",
20+
"- [Azure CLI](https://learn.microsoft.com/cli/azure/install-azure-cli) installed and [Signed into your Azure subscription](https://learn.microsoft.com/cli/azure/authenticate-azure-cli-interactively)\n",
21+
"\n",
22+
"▶️ Click `Run All` to execute all steps sequentially, or execute them `Step by Step`... \n"
2223
]
2324
},
2425
{
@@ -30,7 +31,7 @@
3031
"\n",
3132
"- Resources will be suffixed by a unique string based on your subscription id.\n",
3233
"- Adjust the location parameters according your preferences and on the [product availability by Azure region.](https://azure.microsoft.com/explore/global-infrastructure/products-by-region/?cdn=disable&products=cognitive-services,api-management) \n",
33-
"- Adjust the OpenAI model and version according the [availability by region.](https://learn.microsoft.com/azure/ai-services/openai/concepts/models) "
34+
"- Adjust the models and versions according the [availability by region.](https://learn.microsoft.com/azure/ai-services/openai/concepts/models) "
3435
]
3536
},
3637
{
@@ -47,19 +48,21 @@
4748
"\n",
4849
"deployment_name = os.path.basename(os.path.dirname(globals()['__vsc_ipynb_file__']))\n",
4950
"resource_group_name = f\"lab-{deployment_name}\" # change the name to match your naming style\n",
50-
"app_registration_name = f\"{deployment_name}-app\" # name of the app that will be registered in Microsoft Entra ID\n",
5151
"resource_group_location = \"uksouth\"\n",
5252
"\n",
53+
"aiservices_config = [{\"name\": \"foundry1\", \"location\": \"uksouth\"}]\n",
54+
"\n",
55+
"models_config = [{\"name\": \"gpt-4o-mini\", \"publisher\": \"OpenAI\", \"version\": \"2024-07-18\", \"sku\": \"GlobalStandard\", \"capacity\": 100}]\n",
56+
"\n",
5357
"apim_sku = 'Basicv2'\n",
58+
"apim_subscriptions_config = [{\"name\": \"subscription1\", \"displayName\": \"Subscription 1\"}]\n",
5459
"\n",
55-
"openai_resources = [ {\"name\": \"openai1\", \"location\": \"uksouth\"}]\n",
60+
"inference_api_path = \"inference\" # path to the inference API in the APIM service\n",
61+
"inference_api_type = \"AzureOpenAI\" # options: AzureOpenAI, AzureAI, OpenAI, PassThrough\n",
62+
"inference_api_version = \"2025-03-01-preview\"\n",
63+
"foundry_project_name = deployment_name\n",
5664
"\n",
57-
"openai_model_name = \"gpt-4o-mini\"\n",
58-
"openai_model_version = \"2024-07-18\"\n",
59-
"openai_model_sku = \"GlobalStandard\"\n",
60-
"openai_model_capacity = 20\n",
61-
"openai_deployment_name = \"gpt-4o-mini\"\n",
62-
"openai_api_version = \"2024-10-21\"\n",
65+
"app_registration_name = f\"{deployment_name}-app\" # name of the app that will be registered in Microsoft Entra ID\n",
6366
"\n",
6467
"utils.print_ok('Notebook initialized')\n"
6568
]
@@ -127,13 +130,12 @@
127130
" \"contentVersion\": \"1.0.0.0\",\n",
128131
" \"parameters\": {\n",
129132
" \"apimSku\": { \"value\": apim_sku },\n",
130-
" \"openAIConfig\": { \"value\": openai_resources },\n",
131-
" \"openAIDeploymentName\": { \"value\": openai_deployment_name },\n",
132-
" \"openAIModelName\": { \"value\": openai_model_name },\n",
133-
" \"openAIModelVersion\": { \"value\": openai_model_version },\n",
134-
" \"openAIModelCapacity\": { \"value\": openai_model_capacity },\n",
135-
" \"openAIModelSKU\": { \"value\": openai_model_sku },\n",
136-
" \"openAIAPIVersion\": { \"value\": openai_api_version },\n",
133+
" \"aiServicesConfig\": { \"value\": aiservices_config },\n",
134+
" \"modelsConfig\": { \"value\": models_config },\n",
135+
" \"apimSubscriptionsConfig\": { \"value\": apim_subscriptions_config },\n",
136+
" \"inferenceAPIPath\": { \"value\": inference_api_path },\n",
137+
" \"inferenceAPIType\": { \"value\": inference_api_type },\n",
138+
" \"foundryProjectName\": { \"value\": foundry_project_name },\n",
137139
" \"tenantId\": { \"value\": tenant_id },\n",
138140
" \"clientId\": { \"value\": client_id }\n",
139141
" }\n",
@@ -168,10 +170,16 @@
168170
"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",
169171
"\n",
170172
"if output.success and output.json_data:\n",
173+
" log_analytics_id = utils.get_deployment_output(output, 'logAnalyticsWorkspaceId', 'Log Analytics Id')\n",
171174
" apim_service_id = utils.get_deployment_output(output, 'apimServiceId', 'APIM Service Id')\n",
172175
" apim_resource_gateway_url = utils.get_deployment_output(output, 'apimResourceGatewayURL', 'APIM API Gateway URL')\n",
173-
" apim_subscription_key = utils.get_deployment_output(output, 'apimSubscriptionKey', 'APIM Subscription Key (masked)', True)\n",
174-
"\n"
176+
" apim_subscriptions = json.loads(utils.get_deployment_output(output, 'apimSubscriptions').replace(\"\\'\", \"\\\"\"))\n",
177+
" for subscription in apim_subscriptions:\n",
178+
" subscription_name = subscription['name']\n",
179+
" subscription_key = subscription['key']\n",
180+
" utils.print_info(f\"Subscription Name: {subscription_name}\")\n",
181+
" utils.print_info(f\"Subscription Key: ****{subscription_key[-4:]}\")\n",
182+
" api_key = apim_subscriptions[0].get(\"key\") # default api key to the first subscription key"
175183
]
176184
},
177185
{
@@ -233,13 +241,19 @@
233241
"metadata": {},
234242
"outputs": [],
235243
"source": [
236-
"import requests\n",
244+
"import requests, base64, json\n",
237245
"\n",
238246
"result = app.acquire_token_by_device_flow(flow)\n",
239247
"\n",
240248
"if \"access_token\" in result:\n",
241249
" access_token = result['access_token']\n",
242250
" # Calling graph using the access token\n",
251+
" header, payload, signature = access_token.split('.')\n",
252+
" def pad(b): return b + '=' * (-len(b) % 4)\n",
253+
" print(\"Decoded JWT Header and Payload:\")\n",
254+
" print(json.dumps(json.loads(base64.urlsafe_b64decode(pad(header)).decode('utf-8')), indent=4))\n",
255+
" print(json.dumps(json.loads(base64.urlsafe_b64decode(pad(payload)).decode('utf-8')), indent=4))\n",
256+
"\n",
243257
" graph_data = requests.get( # Use token to call downstream service\n",
244258
" \"https://graph.microsoft.com/v1.0/me\",\n",
245259
" headers={'Authorization': 'Bearer ' + access_token},).json()\n",
@@ -248,16 +262,16 @@
248262
"else:\n",
249263
" print(result.get(\"error\"))\n",
250264
" print(result.get(\"error_description\"))\n",
251-
" print(result.get(\"correlation_id\")) # You may need this when reporting a bug"
265+
" print(result.get(\"correlation_id\")) # You may need this when reporting a bug\n",
266+
"\n"
252267
]
253268
},
254269
{
255270
"cell_type": "markdown",
256271
"metadata": {},
257272
"source": [
258273
"<a id='requests'></a>\n",
259-
"### 🧪 Test the API using a direct HTTP call\n",
260-
"Requests is an elegant and simple HTTP library for Python that will be used here to make raw API requests and inspect the responses.\n",
274+
"### 🧪 Test the API using the access token\n",
261275
"\n",
262276
"Tip: Use the [tracing tool](../../tools/tracing.ipynb) to debug the policy."
263277
]
@@ -268,14 +282,14 @@
268282
"metadata": {},
269283
"outputs": [],
270284
"source": [
271-
"url = apim_resource_gateway_url + \"/openai/deployments/\" + openai_deployment_name + \"/chat/completions?api-version=\" + openai_api_version\n",
285+
"url = f\"{apim_resource_gateway_url}/{inference_api_path}/openai/deployments/{models_config[0]['name']}/chat/completions?api-version={inference_api_version}\"\n",
272286
"\n",
273287
"messages = { \"messages\": [\n",
274288
" {\"role\": \"system\", \"content\": \"You are a sarcastic unhelpful assistant.\"},\n",
275289
" {\"role\": \"user\", \"content\": \"Can you tell me the time, please?\"}\n",
276290
"]}\n",
277291
"\n",
278-
"response = requests.post(url, headers = {'api-key': apim_subscription_key, 'Authorization': 'Bearer ' + access_token}, json = messages)\n",
292+
"response = requests.post(url, headers = {'api-key': api_key, 'Authorization': 'Bearer ' + access_token}, json = messages)\n",
279293
"utils.print_response_code(response)\n",
280294
"\n",
281295
"if (response.status_code == 200):\n",
@@ -304,12 +318,12 @@
304318
"from openai import AzureOpenAI\n",
305319
"\n",
306320
"client = AzureOpenAI(\n",
307-
" azure_endpoint = apim_resource_gateway_url,\n",
308-
" api_key = apim_subscription_key,\n",
309-
" api_version = openai_api_version\n",
321+
" azure_endpoint = f\"{apim_resource_gateway_url}/{inference_api_path}\",\n",
322+
" api_key = api_key,\n",
323+
" api_version = inference_api_version\n",
310324
")\n",
311325
"\n",
312-
"response = client.chat.completions.create(model = openai_model_name, messages = [\n",
326+
"response = client.chat.completions.create(model = models_config[0]['name'], messages = [\n",
313327
" {\"role\": \"system\", \"content\": \"You are a sarcastic unhelpful assistant.\"},\n",
314328
" {\"role\": \"user\", \"content\": \"Can you tell me the time, please?\"}\n",
315329
" ], \n",
@@ -346,7 +360,7 @@
346360
"name": "python",
347361
"nbconvert_exporter": "python",
348362
"pygments_lexer": "ipython3",
349-
"version": "3.12.9"
363+
"version": "3.12.10"
350364
}
351365
},
352366
"nbformat": 4,

labs/access-controlling/clean-up-resources.ipynb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@
6565
"name": "python",
6666
"nbconvert_exporter": "python",
6767
"pygments_lexer": "ipython3",
68-
"version": "3.12.9"
68+
"version": "3.12.10"
6969
}
7070
},
7171
"nbformat": 4,

labs/access-controlling/main.bicep

Lines changed: 51 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -2,66 +2,79 @@
22
// PARAMETERS
33
// ------------------
44

5-
// Typically, parameters would be decorated with appropriate metadata and attributes, but as they are very repetetive in these labs we omit them for brevity.
6-
5+
param aiServicesConfig array = []
6+
param modelsConfig array = []
77
param apimSku string
8-
param openAIConfig array = []
9-
param openAIModelName string
10-
param openAIModelVersion string
11-
param openAIDeploymentName string
12-
param openAIModelSKU string
13-
param openAIModelCapacity int
14-
param openAIAPIVersion string
8+
param apimSubscriptionsConfig array = []
9+
param inferenceAPIType string = 'AzureOpenAI'
10+
param inferenceAPIPath string = 'inference' // Path to the inference API in the APIM service
11+
param foundryProjectName string = 'default'
1512
param tenantId string
1613
param clientId string
1714

1815
// ------------------
19-
// VARIABLES
16+
// RESOURCES
2017
// ------------------
2118

22-
// Account for all placeholders in the polixy.xml file.
23-
var policyXml = loadTextContent('policy.xml')
24-
var updatedPolicyXml = replace(replace(replace(policyXml, '{backend-id}', (length(openAIConfig) > 1) ? 'openai-backend-pool' : openAIConfig[0].name), '{tenant-id}', tenantId), '{client-application-id}', clientId)
19+
// 1. Log Analytics Workspace
20+
module lawModule '../../modules/operational-insights/v1/workspaces.bicep' = {
21+
name: 'lawModule'
22+
}
2523

26-
// ------------------
27-
// RESOURCES
28-
// ------------------
24+
// 2. Application Insights
25+
module appInsightsModule '../../modules/monitor/v1/appinsights.bicep' = {
26+
name: 'appInsightsModule'
27+
params: {
28+
lawId: lawModule.outputs.id
29+
customMetricsOptedInType: 'WithDimensions'
30+
}
31+
}
2932

30-
// 1. API Management
31-
module apimModule '../../modules/apim/v1/apim.bicep' = {
33+
// 3. API Management
34+
module apimModule '../../modules/apim/v2/apim.bicep' = {
3235
name: 'apimModule'
3336
params: {
3437
apimSku: apimSku
38+
apimSubscriptionsConfig: apimSubscriptionsConfig
39+
lawId: lawModule.outputs.id
40+
appInsightsId: appInsightsModule.outputs.id
41+
appInsightsInstrumentationKey: appInsightsModule.outputs.instrumentationKey
3542
}
3643
}
3744

38-
// 2. Cognitive Services
39-
module openAIModule '../../modules/cognitive-services/v1/openai.bicep' = {
40-
name: 'openAIModule'
41-
params: {
42-
openAIConfig: openAIConfig
43-
openAIDeploymentName: openAIDeploymentName
44-
openAIModelName: openAIModelName
45-
openAIModelVersion: openAIModelVersion
46-
openAIModelSKU: openAIModelSKU
47-
openAIModelCapacity: openAIModelCapacity
48-
apimPrincipalId: apimModule.outputs.principalId
45+
// 4. AI Foundry
46+
module foundryModule '../../modules/cognitive-services/v3/foundry.bicep' = {
47+
name: 'foundryModule'
48+
params: {
49+
aiServicesConfig: aiServicesConfig
50+
modelsConfig: modelsConfig
51+
apimPrincipalId: apimModule.outputs.principalId
52+
foundryProjectName: foundryProjectName
53+
}
4954
}
50-
}
5155

52-
// 3. APIM OpenAI API
53-
module openAIAPIModule '../../modules/apim/v1/openai-api.bicep' = {
54-
name: 'openAIAPIModule'
56+
// 5. APIM Inference API
57+
module inferenceAPIModule '../../modules/apim/v2/inference-api.bicep' = {
58+
name: 'inferenceAPIModule'
5559
params: {
56-
policyXml: updatedPolicyXml
57-
openAIConfig: openAIModule.outputs.extendedOpenAIConfig
58-
openAIAPIVersion: openAIAPIVersion
60+
policyXml: replace(replace(loadTextContent('policy.xml'), '{tenant-id}', tenantId), '{client-application-id}', clientId)
61+
apimLoggerId: apimModule.outputs.loggerId
62+
aiServicesConfig: foundryModule.outputs.extendedAIServicesConfig
63+
inferenceAPIType: inferenceAPIType
64+
inferenceAPIPath: inferenceAPIPath
5965
}
6066
}
6167

68+
6269
// ------------------
63-
// MARK: OUTPUTS
70+
// OUTPUTS
6471
// ------------------
72+
73+
output logAnalyticsWorkspaceId string = lawModule.outputs.customerId
6574
output apimServiceId string = apimModule.outputs.id
6675
output apimResourceGatewayURL string = apimModule.outputs.gatewayUrl
67-
output apimSubscriptionKey string = openAIAPIModule.outputs.subscriptionPrimaryKey
76+
77+
output apimSubscriptions array = apimModule.outputs.apimSubscriptions
78+
79+
80+

0 commit comments

Comments
 (0)