|
4 | 4 | "cell_type": "markdown",
|
5 | 5 | "metadata": {},
|
6 | 6 | "source": [
|
7 |
| - "# APIM ❤️ OpenAI\n", |
| 7 | + "# APIM ❤️ AI Foundry\n", |
8 | 8 | "\n",
|
9 | 9 | "## Access Controlling lab\n",
|
10 | 10 | "\n",
|
11 | 11 | "\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", |
13 | 13 | "\n",
|
14 | 14 | "### Prerequisites\n",
|
| 15 | + "\n", |
15 | 16 | "- [Python 3.12 or later version](https://www.python.org/) installed\n",
|
16 |
| - "- [Pandas Library](https://pandas.pydata.org/) installed\n", |
17 | 17 | "- [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" |
22 | 23 | ]
|
23 | 24 | },
|
24 | 25 | {
|
|
30 | 31 | "\n",
|
31 | 32 | "- Resources will be suffixed by a unique string based on your subscription id.\n",
|
32 | 33 | "- 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) " |
34 | 35 | ]
|
35 | 36 | },
|
36 | 37 | {
|
|
47 | 48 | "\n",
|
48 | 49 | "deployment_name = os.path.basename(os.path.dirname(globals()['__vsc_ipynb_file__']))\n",
|
49 | 50 | "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", |
51 | 51 | "resource_group_location = \"uksouth\"\n",
|
52 | 52 | "\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", |
53 | 57 | "apim_sku = 'Basicv2'\n",
|
| 58 | + "apim_subscriptions_config = [{\"name\": \"subscription1\", \"displayName\": \"Subscription 1\"}]\n", |
54 | 59 | "\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", |
56 | 64 | "\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", |
63 | 66 | "\n",
|
64 | 67 | "utils.print_ok('Notebook initialized')\n"
|
65 | 68 | ]
|
|
127 | 130 | " \"contentVersion\": \"1.0.0.0\",\n",
|
128 | 131 | " \"parameters\": {\n",
|
129 | 132 | " \"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", |
137 | 139 | " \"tenantId\": { \"value\": tenant_id },\n",
|
138 | 140 | " \"clientId\": { \"value\": client_id }\n",
|
139 | 141 | " }\n",
|
|
168 | 170 | "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",
|
169 | 171 | "\n",
|
170 | 172 | "if output.success and output.json_data:\n",
|
| 173 | + " log_analytics_id = utils.get_deployment_output(output, 'logAnalyticsWorkspaceId', 'Log Analytics Id')\n", |
171 | 174 | " apim_service_id = utils.get_deployment_output(output, 'apimServiceId', 'APIM Service Id')\n",
|
172 | 175 | " 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" |
175 | 183 | ]
|
176 | 184 | },
|
177 | 185 | {
|
|
233 | 241 | "metadata": {},
|
234 | 242 | "outputs": [],
|
235 | 243 | "source": [
|
236 |
| - "import requests\n", |
| 244 | + "import requests, base64, json\n", |
237 | 245 | "\n",
|
238 | 246 | "result = app.acquire_token_by_device_flow(flow)\n",
|
239 | 247 | "\n",
|
240 | 248 | "if \"access_token\" in result:\n",
|
241 | 249 | " access_token = result['access_token']\n",
|
242 | 250 | " # 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", |
243 | 257 | " graph_data = requests.get( # Use token to call downstream service\n",
|
244 | 258 | " \"https://graph.microsoft.com/v1.0/me\",\n",
|
245 | 259 | " headers={'Authorization': 'Bearer ' + access_token},).json()\n",
|
|
248 | 262 | "else:\n",
|
249 | 263 | " print(result.get(\"error\"))\n",
|
250 | 264 | " 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" |
252 | 267 | ]
|
253 | 268 | },
|
254 | 269 | {
|
255 | 270 | "cell_type": "markdown",
|
256 | 271 | "metadata": {},
|
257 | 272 | "source": [
|
258 | 273 | "<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", |
261 | 275 | "\n",
|
262 | 276 | "Tip: Use the [tracing tool](../../tools/tracing.ipynb) to debug the policy."
|
263 | 277 | ]
|
|
268 | 282 | "metadata": {},
|
269 | 283 | "outputs": [],
|
270 | 284 | "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", |
272 | 286 | "\n",
|
273 | 287 | "messages = { \"messages\": [\n",
|
274 | 288 | " {\"role\": \"system\", \"content\": \"You are a sarcastic unhelpful assistant.\"},\n",
|
275 | 289 | " {\"role\": \"user\", \"content\": \"Can you tell me the time, please?\"}\n",
|
276 | 290 | "]}\n",
|
277 | 291 | "\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", |
279 | 293 | "utils.print_response_code(response)\n",
|
280 | 294 | "\n",
|
281 | 295 | "if (response.status_code == 200):\n",
|
|
304 | 318 | "from openai import AzureOpenAI\n",
|
305 | 319 | "\n",
|
306 | 320 | "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", |
310 | 324 | ")\n",
|
311 | 325 | "\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", |
313 | 327 | " {\"role\": \"system\", \"content\": \"You are a sarcastic unhelpful assistant.\"},\n",
|
314 | 328 | " {\"role\": \"user\", \"content\": \"Can you tell me the time, please?\"}\n",
|
315 | 329 | " ], \n",
|
|
346 | 360 | "name": "python",
|
347 | 361 | "nbconvert_exporter": "python",
|
348 | 362 | "pygments_lexer": "ipython3",
|
349 |
| - "version": "3.12.9" |
| 363 | + "version": "3.12.10" |
350 | 364 | }
|
351 | 365 | },
|
352 | 366 | "nbformat": 4,
|
|
0 commit comments