diff --git a/labs/openai-agents-tf/README.MD b/labs/openai-agents-tf/README.MD new file mode 100644 index 0000000..64d4fef --- /dev/null +++ b/labs/openai-agents-tf/README.MD @@ -0,0 +1,28 @@ +# APIM ❤️ AI Agents + +## [OpenAI Agents lab](openai-agents.ipynb) (with Terraform) + +[![flow](../../images/openai-agents.gif)](openai-agents.ipynb) + +Playground to try the [OpenAI Agents](https://openai.github.io/openai-agents-python/) with Azure OpenAI models and API based tools through Azure API Management. This enables limitless opportunities for AI agents while maintaining control through Azure API Management! + +### Prerequisites + +- [Python 3.12 or later version](https://www.python.org/) installed +- [VS Code](https://code.visualstudio.com/) installed with the [Jupyter notebook extension](https://marketplace.visualstudio.com/items?itemName=ms-toolsai.jupyter) enabled +- [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 +- [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 +- [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) +- [Terraform CLI](https://learn.hashicorp.com/tutorials/terraform/install-cli) installed + + +▶️ Click `Run All` to execute all steps sequentially, or execute them `Step by Step`... + +### 🚀 Get started + +Proceed by opening the [Jupyter notebook](openai-agents-tf.ipynb), and follow the steps provided. + +### 🗑️ Clean up resources + +When you're finished with the lab, you should remove all your deployed resources from Azure to avoid extra charges and keep your Azure subscription uncluttered. +Use the [clean-up-resources notebook](clean-up-resources.ipynb) for that. diff --git a/labs/openai-agents-tf/city-weather-mock-policy.xml b/labs/openai-agents-tf/city-weather-mock-policy.xml new file mode 100644 index 0000000..b5fd1e7 --- /dev/null +++ b/labs/openai-agents-tf/city-weather-mock-policy.xml @@ -0,0 +1,44 @@ + + + + + + @{ + var random = new Random(); + double temperature; + var format = "Celsius"; + var descriptions = new[] { "Clear skies", "Partly cloudy", "Overcast", "Rainy" }; + var city = context.Request.MatchedParameters["city"]; + switch (city.ToLower()) + { + case "seattle": + case "new york city": + case "los angeles": + format = "Fahrenheit"; + temperature = random.Next(14, 95) + random.NextDouble(); + break; + default: + temperature = random.Next(-5, 35) + random.NextDouble(); + break; + } + return new JObject( + new JProperty("city", city), + new JProperty("temperature", Math.Round(temperature, 1)), + new JProperty("temperature_format", format), + new JProperty("description", descriptions[random.Next(descriptions.Length)]), + new JProperty("humidity", random.Next(20, 100)), + new JProperty("wind_speed", Math.Round(random.NextDouble() * 10, 1)) + ).ToString(); + } + + + + + + + + + + + + \ No newline at end of file diff --git a/labs/openai-agents-tf/city-weather-openapi.json b/labs/openai-agents-tf/city-weather-openapi.json new file mode 100644 index 0000000..176b33d --- /dev/null +++ b/labs/openai-agents-tf/city-weather-openapi.json @@ -0,0 +1,120 @@ +{ + "openapi": "3.0.1", + "info": { + "title": "City Weather API", + "description": "API to retrieve weather information, including the current temperature, for a specified city. Generated with GitHub Copilot and the following prompt - create an openapi file that retrieves weather information with the city query parameter and reply with temperature. Add examples to the specification.", + "version": "1.0" + }, + "servers": [{ + "url": "https://replace-me.local/weatherservice" + }], + "paths": { + "/weather": { + "get": { + "summary": "Get current weather information for a specified city", + "description": "Retrieves weather information, including the current temperature, for a specified city.", + "operationId": "get-weather-city-city", + "parameters": [{ + "name": "city", + "in": "query", + "description": "Name of the city to retrieve weather information for", + "required": true, + "schema": { + "type": "string", + "example": "Tokyo" + } + }], + "responses": { + "200": { + "description": "Weather information for the specified city", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CityWeather" + }, + "examples": { + "Tokyo": { + "summary": "Example response for Tokyo", + "value": { + "city": "Tokyo", + "temperature": 15.5, + "temperature_format": "Celsius", + "description": "Clear sky", + "humidity": 60, + "wind_speed": 5.5 + } + }, + "default": { + "value": { + "city": "New York City", + "temperature": 59, + "temperature_format": "Fahrenheit", + "description": "Partly cloudy", + "humidity": 70, + "wind_speed": 3.2 + } + } + } + } + } + }, + "400": { + "description": "Bad request" + }, + "404": { + "description": "City not found" + }, + "500": { + "description": "Internal server error" + } + } + } + } + }, + "components": { + "schemas": { + "CityWeather": { + "type": "object", + "properties": { + "city": { + "type": "string", + "example": "Tokyo" + }, + "temperature": { + "type": "number", + "format": "float", + "example": 15.5 + }, + "temperature_format": { + "type": "string", + "example": "Celsius" + }, + "description": { + "type": "string", + "example": "Clear sky" + }, + "humidity": { + "type": "number", + "format": "int32", + "example": 60 + }, + "wind_speed": { + "type": "number", + "format": "float", + "example": 5.5 + } + } + } + }, + "securitySchemes": { + "apiKeyHeader": { + "type": "apiKey", + "name": "api-key", + "in": "header" + } + } + }, + "security": [{ + "apiKeyHeader": [] + }] +} \ No newline at end of file diff --git a/labs/openai-agents-tf/clean-up-resources.ipynb b/labs/openai-agents-tf/clean-up-resources.ipynb new file mode 100644 index 0000000..8328e58 --- /dev/null +++ b/labs/openai-agents-tf/clean-up-resources.ipynb @@ -0,0 +1,61 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 🗑️ Clean up resources\n", + "\n", + "When you're finished with the lab, you should remove all your deployed resources from Azure to avoid extra charges and keep your Azure subscription uncluttered." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import os, sys\n", + "sys.path.insert(1, '../../shared') # add the shared directory to the Python path\n", + "import utils\n", + "\n", + "output = utils.run(\"az account show\", \"Retrieved az account\", \"Failed to get the current az account\")\n", + "\n", + "subscription_id = ''\n", + "if output.success and output.json_data:\n", + " subscription_id = output.json_data['id']\n", + "\n", + "# Specify the target subscription for Terraform\n", + "os.environ['ARM_SUBSCRIPTION_ID'] = subscription_id\n", + "\n", + "# Intialize terraform\n", + "output = utils.run(\n", + " f\"terraform destroy -auto-approve\",\n", + " f\"Resources deletion succeeded\",\n", + " f\"Resources deletion failed\",\n", + ")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.8" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/labs/openai-agents-tf/data.tf b/labs/openai-agents-tf/data.tf new file mode 100644 index 0000000..bb087fd --- /dev/null +++ b/labs/openai-agents-tf/data.tf @@ -0,0 +1 @@ +data "azurerm_client_config" "current" {} \ No newline at end of file diff --git a/labs/openai-agents-tf/inference-openapi.json b/labs/openai-agents-tf/inference-openapi.json new file mode 100644 index 0000000..a966b25 --- /dev/null +++ b/labs/openai-agents-tf/inference-openapi.json @@ -0,0 +1,1809 @@ +{ + "swagger": "2.0", + "info": { + "title": "AI Model Inference", + "version": "2024-05-01-preview", + "x-typespec-generated": [ + { + "emitter": "@azure-tools/typespec-autorest" + } + ] + }, + "schemes": [ + "https" + ], + "produces": [ + "application/json" + ], + "consumes": [ + "application/json" + ], + "security": [ + { + "ApiKeyAuth": [] + }, + { + "OAuth2Auth": [ + "https://ml.azure.com/.default" + ] + } + ], + "securityDefinitions": { + "ApiKeyAuth": { + "type": "apiKey", + "name": "api-key", + "in": "header" + }, + "OAuth2Auth": { + "type": "oauth2", + "flow": "implicit", + "authorizationUrl": "https://login.microsoftonline.com/common/oauth2/v2.0/authorize", + "scopes": { + "https://ml.azure.com/.default": "" + } + } + }, + "tags": [], + "paths": { + "/chat/completions": { + "post": { + "operationId": "GetChatCompletions", + "description": "Gets chat completions for the provided chat messages.\nCompletions support a wide variety of tasks and generate text that continues from or \"completes\"\nprovided prompt data. The method makes a REST API call to the `/chat/completions` route\non the given endpoint.", + "parameters": [ + { + "name": "extra-parameters", + "in": "header", + "description": "Controls what happens if extra parameters, undefined by the REST API,\nare passed in the JSON request payload.\nThis sets the HTTP request header `extra-parameters`.", + "required": false, + "type": "string", + "enum": [ + "error", + "drop", + "pass-through" + ], + "x-ms-enum": { + "name": "ExtraParameters", + "modelAsString": true, + "values": [ + { + "name": "error", + "value": "error", + "description": "The service will error if it detected extra parameters in the request payload. This is the service default." + }, + { + "name": "drop", + "value": "drop", + "description": "The service will ignore (drop) extra parameters in the request payload. It will only pass the known parameters to the back-end AI model." + }, + { + "name": "pass_through", + "value": "pass-through", + "description": "The service will pass extra parameters to the back-end AI model." + } + ] + }, + "x-ms-client-name": "extra_params" + }, + { + "name": "body", + "in": "body", + "description": "The parameters of the chat completions request.", + "required": true, + "schema": { + "$ref": "#/definitions/ChatCompletionsOptions" + } + } + ], + "responses": { + "200": { + "description": "The request has succeeded.", + "schema": { + "$ref": "#/definitions/ChatCompletions" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/Azure.Core.Foundations.ErrorResponse" + }, + "headers": { + "x-ms-error-code": { + "type": "string", + "description": "String error code indicating what went wrong." + } + } + } + }, + "x-ms-examples": { + "Audio modality chat completion": { + "$ref": "./examples/GetChatCompletions_AudioModality_Gen.json" + }, + "maximum set chat completion": { + "$ref": "./examples/GetChatCompletions_MaximumSet_Gen.json" + }, + "minimum set chat completion": { + "$ref": "./examples/GetChatCompletions_MinimumSet_Gen.json" + } + } + } + }, + "/embeddings": { + "post": { + "operationId": "GetEmbeddings", + "description": "Return the embedding vectors for given text prompts.\nThe method makes a REST API call to the `/embeddings` route on the given endpoint.", + "parameters": [ + { + "name": "extra-parameters", + "in": "header", + "description": "Controls what happens if extra parameters, undefined by the REST API,\nare passed in the JSON request payload.\nThis sets the HTTP request header `extra-parameters`.", + "required": false, + "type": "string", + "enum": [ + "error", + "drop", + "pass-through" + ], + "x-ms-enum": { + "name": "ExtraParameters", + "modelAsString": true, + "values": [ + { + "name": "error", + "value": "error", + "description": "The service will error if it detected extra parameters in the request payload. This is the service default." + }, + { + "name": "drop", + "value": "drop", + "description": "The service will ignore (drop) extra parameters in the request payload. It will only pass the known parameters to the back-end AI model." + }, + { + "name": "pass_through", + "value": "pass-through", + "description": "The service will pass extra parameters to the back-end AI model." + } + ] + }, + "x-ms-client-name": "extra_params" + }, + { + "name": "body", + "in": "body", + "description": "The parameters of the embeddings request.", + "required": true, + "schema": { + "$ref": "#/definitions/EmbeddingsOptions" + } + } + ], + "responses": { + "200": { + "description": "The request has succeeded.", + "schema": { + "$ref": "#/definitions/EmbeddingsResult" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/Azure.Core.Foundations.ErrorResponse" + }, + "headers": { + "x-ms-error-code": { + "type": "string", + "description": "String error code indicating what went wrong." + } + } + } + }, + "x-ms-examples": { + "maximum set embeddings": { + "$ref": "./examples/GetEmbeddings_MaximumSet_Gen.json" + }, + "minimum set embeddings": { + "$ref": "./examples/GetEmbeddings_MinimumSet_Gen.json" + } + } + } + }, + "/images/embeddings": { + "post": { + "operationId": "GetImageEmbeddings", + "description": "Return the embedding vectors for given images.\nThe method makes a REST API call to the `/images/embeddings` route on the given endpoint.", + "parameters": [ + { + "name": "extra-parameters", + "in": "header", + "description": "Controls what happens if extra parameters, undefined by the REST API,\nare passed in the JSON request payload.\nThis sets the HTTP request header `extra-parameters`.", + "required": false, + "type": "string", + "enum": [ + "error", + "drop", + "pass-through" + ], + "x-ms-enum": { + "name": "ExtraParameters", + "modelAsString": true, + "values": [ + { + "name": "error", + "value": "error", + "description": "The service will error if it detected extra parameters in the request payload. This is the service default." + }, + { + "name": "drop", + "value": "drop", + "description": "The service will ignore (drop) extra parameters in the request payload. It will only pass the known parameters to the back-end AI model." + }, + { + "name": "pass_through", + "value": "pass-through", + "description": "The service will pass extra parameters to the back-end AI model." + } + ] + }, + "x-ms-client-name": "extra_params" + }, + { + "name": "body", + "in": "body", + "description": "The parameters of the image embeddings request.", + "required": true, + "schema": { + "$ref": "#/definitions/ImageEmbeddingsOptions" + } + } + ], + "responses": { + "200": { + "description": "The request has succeeded.", + "schema": { + "$ref": "#/definitions/EmbeddingsResult" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/Azure.Core.Foundations.ErrorResponse" + }, + "headers": { + "x-ms-error-code": { + "type": "string", + "description": "String error code indicating what went wrong." + } + } + } + }, + "x-ms-examples": { + "maximum set image embeddings": { + "$ref": "./examples/GetImageEmbeddings_MaximumSet_Gen.json" + }, + "minimum set image embeddings": { + "$ref": "./examples/GetImageEmbeddings_MinimumSet_Gen.json" + } + } + } + }, + "/info": { + "get": { + "operationId": "GetModelInfo", + "description": "Returns information about the AI model.\nThe method makes a REST API call to the `/info` route on the given endpoint.\nThis method will only work when using Serverless API or Managed Compute endpoint.\nIt will not work for GitHub Models endpoint or Azure OpenAI endpoint.", + "responses": { + "200": { + "description": "The request has succeeded.", + "schema": { + "$ref": "#/definitions/ModelInfo" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/Azure.Core.Foundations.ErrorResponse" + }, + "headers": { + "x-ms-error-code": { + "type": "string", + "description": "String error code indicating what went wrong." + } + } + } + }, + "x-ms-examples": { + "maximum set model information": { + "$ref": "./examples/GetModelInfo_MaximumSet_Gen.json" + }, + "minimum set model information": { + "$ref": "./examples/GetModelInfo_MinimumSet_Gen.json" + } + } + } + } + }, + "definitions": { + "AudioContentFormat": { + "type": "string", + "description": "A representation of the possible audio formats for audio.", + "enum": [ + "wav", + "mp3" + ], + "x-ms-enum": { + "name": "AudioContentFormat", + "modelAsString": true, + "values": [ + { + "name": "wav", + "value": "wav", + "description": "Specifies audio in WAV format." + }, + { + "name": "mp3", + "value": "mp3", + "description": "Specifies audio in MP3 format." + } + ] + } + }, + "Azure.Core.Foundations.Error": { + "type": "object", + "description": "The error object.", + "properties": { + "code": { + "type": "string", + "description": "One of a server-defined set of error codes." + }, + "message": { + "type": "string", + "description": "A human-readable representation of the error." + }, + "target": { + "type": "string", + "description": "The target of the error." + }, + "details": { + "type": "array", + "description": "An array of details about specific errors that led to this reported error.", + "items": { + "$ref": "#/definitions/Azure.Core.Foundations.Error" + }, + "x-ms-identifiers": [] + }, + "innererror": { + "$ref": "#/definitions/Azure.Core.Foundations.InnerError", + "description": "An object containing more specific information than the current object about the error." + } + }, + "required": [ + "code", + "message" + ] + }, + "Azure.Core.Foundations.ErrorResponse": { + "type": "object", + "description": "A response containing error details.", + "properties": { + "error": { + "$ref": "#/definitions/Azure.Core.Foundations.Error", + "description": "The error object." + } + }, + "required": [ + "error" + ] + }, + "Azure.Core.Foundations.InnerError": { + "type": "object", + "description": "An object containing more specific information about the error. As per Microsoft One API guidelines - https://github.com/Microsoft/api-guidelines/blob/vNext/Guidelines.md#7102-error-condition-responses.", + "properties": { + "code": { + "type": "string", + "description": "One of a server-defined set of error codes." + }, + "innererror": { + "$ref": "#/definitions/Azure.Core.Foundations.InnerError", + "description": "Inner error." + } + } + }, + "ChatChoice": { + "type": "object", + "description": "The representation of a single prompt completion as part of an overall chat completions request.\nGenerally, `n` choices are generated per provided prompt with a default value of 1.\nToken limits and other settings may limit the number of choices generated.", + "properties": { + "index": { + "type": "integer", + "format": "int32", + "description": "The ordered index associated with this chat completions choice." + }, + "finish_reason": { + "$ref": "#/definitions/CompletionsFinishReason", + "description": "The reason that this chat completions choice completed its generated.", + "x-nullable": true + }, + "message": { + "$ref": "#/definitions/ChatResponseMessage", + "description": "The chat message for a given chat completions prompt." + } + }, + "required": [ + "index", + "finish_reason", + "message" + ] + }, + "ChatCompletions": { + "type": "object", + "description": "Representation of the response data from a chat completions request.\nCompletions support a wide variety of tasks and generate text that continues from or \"completes\"\nprovided prompt data.", + "properties": { + "id": { + "type": "string", + "description": "A unique identifier associated with this chat completions response." + }, + "object": { + "type": "string", + "description": "The response object type, which is always `chat.completion`.", + "enum": [ + "chat.completion" + ], + "x-ms-enum": { + "modelAsString": false + } + }, + "created": { + "type": "integer", + "format": "unixtime", + "description": "The first timestamp associated with generation activity for this completions response,\nrepresented as seconds since the beginning of the Unix epoch of 00:00 on 1 Jan 1970." + }, + "model": { + "type": "string", + "description": "The model used for the chat completion." + }, + "choices": { + "type": "array", + "description": "The collection of completions choices associated with this completions response.\nGenerally, `n` choices are generated per provided prompt with a default value of 1.\nToken limits and other settings may limit the number of choices generated.", + "minItems": 1, + "items": { + "$ref": "#/definitions/ChatChoice" + }, + "x-ms-identifiers": [] + }, + "usage": { + "$ref": "#/definitions/CompletionsUsage", + "description": " Usage information for tokens processed and generated as part of this completions operation." + } + }, + "required": [ + "id", + "object", + "created", + "model", + "choices", + "usage" + ] + }, + "ChatCompletionsAudio": { + "type": "object", + "description": "A representation of the audio generated by the model.", + "properties": { + "id": { + "type": "string", + "description": " Unique identifier for the audio response. This value can be used in chat history messages instead of passing \n the full audio object." + }, + "expires_at": { + "type": "integer", + "format": "unixtime", + "description": "The Unix timestamp (in seconds) at which the audio piece expires and can't be any longer referenced by its ID in \nmulti-turn conversations.", + "x-ms-client-name": "expiresAt" + }, + "data": { + "type": "string", + "description": "Base64 encoded audio data" + }, + "format": { + "$ref": "#/definitions/AudioContentFormat", + "description": "The format of the audio content. If format is not provided, it will match the format used in the\ninput audio request." + }, + "transcript": { + "type": "string", + "description": "The transcript of the audio file." + } + }, + "required": [ + "id", + "expires_at", + "data", + "transcript" + ] + }, + "ChatCompletionsModality": { + "type": "string", + "description": "The modalities that the model is allowed to use for the chat completions response.", + "enum": [ + "text", + "audio" + ], + "x-ms-enum": { + "name": "ChatCompletionsModality", + "modelAsString": true, + "values": [ + { + "name": "text", + "value": "text", + "description": "The model is only allowed to generate text." + }, + { + "name": "audio", + "value": "audio", + "description": "The model is allowed to generate audio." + } + ] + } + }, + "ChatCompletionsNamedToolChoice": { + "type": "object", + "description": "A tool selection of a specific, named function tool that will limit chat completions to using the named function.", + "properties": { + "type": { + "type": "string", + "description": "The type of the tool. Currently, only `function` is supported.", + "enum": [ + "function" + ], + "x-ms-enum": { + "modelAsString": false + } + }, + "function": { + "$ref": "#/definitions/ChatCompletionsNamedToolChoiceFunction", + "description": "The function that should be called." + } + }, + "required": [ + "type", + "function" + ] + }, + "ChatCompletionsNamedToolChoiceFunction": { + "type": "object", + "description": "A tool selection of a specific, named function tool that will limit chat completions to using the named function.", + "properties": { + "name": { + "type": "string", + "description": "The name of the function that should be called." + } + }, + "required": [ + "name" + ] + }, + "ChatCompletionsOptions": { + "type": "object", + "description": "The configuration information for a chat completions request.\nCompletions support a wide variety of tasks and generate text that continues from or \"completes\"\nprovided prompt data.", + "properties": { + "messages": { + "type": "array", + "description": "The collection of context messages associated with this chat completions request.\nTypical usage begins with a chat message for the System role that provides instructions for\nthe behavior of the assistant, followed by alternating messages between the User and\nAssistant roles.", + "minItems": 1, + "items": { + "$ref": "#/definitions/ChatRequestMessage" + }, + "x-ms-identifiers": [] + }, + "frequency_penalty": { + "type": "number", + "format": "float", + "description": "A value that influences the probability of generated tokens appearing based on their cumulative\nfrequency in generated text.\nPositive values will make tokens less likely to appear as their frequency increases and\ndecrease the likelihood of the model repeating the same statements verbatim.\nSupported range is [-2, 2].", + "default": 0, + "minimum": -2, + "maximum": 2 + }, + "stream": { + "type": "boolean", + "description": "A value indicating whether chat completions should be streamed for this request." + }, + "presence_penalty": { + "type": "number", + "format": "float", + "description": "A value that influences the probability of generated tokens appearing based on their existing\npresence in generated text.\nPositive values will make tokens less likely to appear when they already exist and increase the\nmodel's likelihood to output new topics.\nSupported range is [-2, 2].", + "default": 0, + "minimum": -2, + "maximum": 2 + }, + "temperature": { + "type": "number", + "format": "float", + "description": "The sampling temperature to use that controls the apparent creativity of generated completions.\nHigher values will make output more random while lower values will make results more focused\nand deterministic.\nIt is not recommended to modify temperature and top_p for the same completions request as the\ninteraction of these two settings is difficult to predict.\nSupported range is [0, 1].", + "default": 0.7, + "minimum": 0, + "maximum": 1 + }, + "top_p": { + "type": "number", + "format": "float", + "description": "An alternative to sampling with temperature called nucleus sampling. This value causes the\nmodel to consider the results of tokens with the provided probability mass. As an example, a\nvalue of 0.15 will cause only the tokens comprising the top 15% of probability mass to be\nconsidered.\nIt is not recommended to modify temperature and top_p for the same completions request as the\ninteraction of these two settings is difficult to predict.\nSupported range is [0, 1].", + "default": 1, + "minimum": 0, + "maximum": 1 + }, + "max_tokens": { + "type": "integer", + "format": "int32", + "description": "The maximum number of tokens to generate.", + "minimum": 0 + }, + "response_format": { + "$ref": "#/definitions/ChatCompletionsResponseFormat", + "description": "An object specifying the format that the model must output.\n\nSetting to `{ \"type\": \"json_schema\", \"json_schema\": {...} }` enables Structured Outputs which ensures the model will match your supplied JSON schema.\n\nSetting to `{ \"type\": \"json_object\" }` enables JSON mode, which ensures the message the model generates is valid JSON.\n\n**Important:** when using JSON mode, you **must** also instruct the model to produce JSON yourself via a system or user message. Without this, the model may generate an unending stream of whitespace until the generation reaches the token limit, resulting in a long-running and seemingly \"stuck\" request. Also note that the message content may be partially cut off if `finish_reason=\"length\"`, which indicates the generation exceeded `max_tokens` or the conversation exceeded the max context length." + }, + "stop": { + "type": "array", + "description": "A collection of textual sequences that will end completions generation.", + "minItems": 1, + "items": { + "type": "string" + } + }, + "tools": { + "type": "array", + "description": "A list of tools the model may request to call. Currently, only functions are supported as a tool. The model\nmay response with a function call request and provide the input arguments in JSON format for that function.", + "minItems": 1, + "items": { + "$ref": "#/definitions/ChatCompletionsToolDefinition" + }, + "x-ms-identifiers": [] + }, + "tool_choice": { + "description": "If specified, the model will configure which of the provided tools it can use for the chat completions response.", + "x-ms-client-name": "toolChoice" + }, + "seed": { + "type": "integer", + "format": "int64", + "description": "If specified, the system will make a best effort to sample deterministically such that repeated requests with the\nsame seed and parameters should return the same result. Determinism is not guaranteed." + }, + "model": { + "type": "string", + "description": "ID of the specific AI model to use, if more than one model is available on the endpoint." + }, + "modalities": { + "type": "array", + "description": "The modalities that the model is allowed to use for the chat completions response. The default modality\nis `text`. Indicating an unsupported modality combination results in an 422 error.", + "items": { + "$ref": "#/definitions/ChatCompletionsModality" + } + } + }, + "required": [ + "messages" + ], + "additionalProperties": {} + }, + "ChatCompletionsResponseFormat": { + "type": "object", + "description": "Represents the format that the model must output. Use this to enable JSON mode instead of the default text mode.\nNote that to enable JSON mode, some AI models may also require you to instruct the model to produce JSON\nvia a system or user message.", + "properties": { + "type": { + "type": "string", + "description": "The response format type to use for chat completions." + } + }, + "discriminator": "type", + "required": [ + "type" + ] + }, + "ChatCompletionsResponseFormatJsonObject": { + "type": "object", + "description": "A response format for Chat Completions that restricts responses to emitting valid JSON objects.\nNote that to enable JSON mode, some AI models may also require you to instruct the model to produce JSON\nvia a system or user message.", + "allOf": [ + { + "$ref": "#/definitions/ChatCompletionsResponseFormat" + } + ], + "x-ms-discriminator-value": "json_object" + }, + "ChatCompletionsResponseFormatJsonSchema": { + "type": "object", + "description": "A response format for Chat Completions that restricts responses to emitting valid JSON objects, with a\nJSON schema specified by the caller.", + "properties": { + "json_schema": { + "$ref": "#/definitions/ChatCompletionsResponseFormatJsonSchemaDefinition", + "description": "The definition of the required JSON schema in the response, and associated metadata." + } + }, + "required": [ + "json_schema" + ], + "allOf": [ + { + "$ref": "#/definitions/ChatCompletionsResponseFormat" + } + ], + "x-ms-discriminator-value": "json_schema" + }, + "ChatCompletionsResponseFormatJsonSchemaDefinition": { + "type": "object", + "description": "The definition of the required JSON schema in the response, and associated metadata.", + "properties": { + "name": { + "type": "string", + "description": "The name of the response format. Must be a-z, A-Z, 0-9, or contain underscores and dashes, with a maximum length of 64." + }, + "schema": { + "type": "object", + "description": "The definition of the JSON schema", + "additionalProperties": {} + }, + "description": { + "type": "string", + "description": "A description of the response format, used by the AI model to determine how to generate responses in this format." + }, + "strict": { + "type": "boolean", + "description": "Whether to enable strict schema adherence when generating the output.\nIf set to true, the model will always follow the exact schema defined in the `schema` field. Only a subset of\nJSON Schema is supported when `strict` is `true`.", + "default": false + } + }, + "required": [ + "name", + "schema" + ] + }, + "ChatCompletionsResponseFormatText": { + "type": "object", + "description": "A response format for Chat Completions that emits text responses. This is the default response format.", + "allOf": [ + { + "$ref": "#/definitions/ChatCompletionsResponseFormat" + } + ], + "x-ms-discriminator-value": "text" + }, + "ChatCompletionsToolCall": { + "type": "object", + "description": "A function tool call requested by the AI model.", + "properties": { + "id": { + "type": "string", + "description": "The ID of the tool call." + }, + "type": { + "type": "string", + "description": "The type of tool call. Currently, only `function` is supported.", + "enum": [ + "function" + ], + "x-ms-enum": { + "modelAsString": false + } + }, + "function": { + "$ref": "#/definitions/FunctionCall", + "description": "The details of the function call requested by the AI model." + } + }, + "required": [ + "id", + "type", + "function" + ] + }, + "ChatCompletionsToolChoicePreset": { + "type": "string", + "description": "Represents a generic policy for how a chat completions tool may be selected.", + "enum": [ + "auto", + "none", + "required" + ], + "x-ms-enum": { + "name": "ChatCompletionsToolChoicePreset", + "modelAsString": true, + "values": [ + { + "name": "auto", + "value": "auto", + "description": "Specifies that the model may either use any of the tools provided in this chat completions request or\ninstead return a standard chat completions response as if no tools were provided." + }, + { + "name": "none", + "value": "none", + "description": "Specifies that the model should not respond with a tool call and should instead provide a standard chat\ncompletions response. Response content may still be influenced by the provided tool definitions." + }, + { + "name": "required", + "value": "required", + "description": "Specifies that the model should respond with a call to one or more tools." + } + ] + } + }, + "ChatCompletionsToolDefinition": { + "type": "object", + "description": "The definition of a chat completions tool that can call a function.", + "properties": { + "type": { + "type": "string", + "description": "The type of the tool. Currently, only `function` is supported.", + "enum": [ + "function" + ], + "x-ms-enum": { + "modelAsString": false + } + }, + "function": { + "$ref": "#/definitions/FunctionDefinition", + "description": "The function definition details for the function tool." + } + }, + "required": [ + "type", + "function" + ] + }, + "ChatMessageAudioContentItem": { + "type": "object", + "description": "A structured chat content item containing an audio reference.", + "properties": { + "audio_url": { + "$ref": "#/definitions/ChatMessageAudioUrl", + "description": "An internet location, which must be accessible to the model, from which the audio may be retrieved.", + "x-ms-client-name": "audioUrl" + } + }, + "required": [ + "audio_url" + ], + "allOf": [ + { + "$ref": "#/definitions/ChatMessageContentItem" + } + ], + "x-ms-discriminator-value": "audio_url" + }, + "ChatMessageAudioUrl": { + "type": "object", + "description": "An internet location from which the model may retrieve an audio.", + "properties": { + "url": { + "type": "string", + "description": "The URL of the audio." + } + }, + "required": [ + "url" + ] + }, + "ChatMessageContentItem": { + "type": "object", + "description": "An abstract representation of a structured content item within a chat message.", + "properties": { + "type": { + "type": "string", + "description": "The discriminated object type." + } + }, + "discriminator": "type", + "required": [ + "type" + ] + }, + "ChatMessageImageContentItem": { + "type": "object", + "description": "A structured chat content item containing an image reference.", + "properties": { + "image_url": { + "$ref": "#/definitions/ChatMessageImageUrl", + "description": "An internet location, which must be accessible to the model,from which the image may be retrieved.", + "x-ms-client-name": "imageUrl" + } + }, + "required": [ + "image_url" + ], + "allOf": [ + { + "$ref": "#/definitions/ChatMessageContentItem" + } + ], + "x-ms-discriminator-value": "image_url" + }, + "ChatMessageImageDetailLevel": { + "type": "string", + "description": "A representation of the possible image detail levels for image-based chat completions message content.", + "enum": [ + "auto", + "low", + "high" + ], + "x-ms-enum": { + "name": "ChatMessageImageDetailLevel", + "modelAsString": true, + "values": [ + { + "name": "auto", + "value": "auto", + "description": "Specifies that the model should determine which detail level to apply using heuristics like image size." + }, + { + "name": "low", + "value": "low", + "description": "Specifies that image evaluation should be constrained to the 'low-res' model that may be faster and consume fewer\ntokens but may also be less accurate for highly detailed images." + }, + { + "name": "high", + "value": "high", + "description": "Specifies that image evaluation should enable the 'high-res' model that may be more accurate for highly detailed\nimages but may also be slower and consume more tokens." + } + ] + } + }, + "ChatMessageImageUrl": { + "type": "object", + "description": "An internet location from which the model may retrieve an image.", + "properties": { + "url": { + "type": "string", + "description": "The URL of the image." + }, + "detail": { + "$ref": "#/definitions/ChatMessageImageDetailLevel", + "description": "The evaluation quality setting to use, which controls relative prioritization of speed, token consumption, and\naccuracy." + } + }, + "required": [ + "url" + ] + }, + "ChatMessageInputAudio": { + "type": "object", + "description": "The details of an audio chat message content part.", + "properties": { + "data": { + "type": "string", + "description": "Base64 encoded audio data" + }, + "format": { + "$ref": "#/definitions/AudioContentFormat", + "description": "The audio format of the audio content." + } + }, + "required": [ + "data", + "format" + ] + }, + "ChatMessageInputAudioContentItem": { + "type": "object", + "description": "A structured chat content item containing an audio content.", + "properties": { + "format": { + "$ref": "#/definitions/AudioContentFormat", + "description": "The audio format of the audio reference." + } + }, + "required": [ + "format" + ], + "allOf": [ + { + "$ref": "#/definitions/ChatMessageContentItem" + } + ], + "x-ms-discriminator-value": "input_audio" + }, + "ChatMessageTextContentItem": { + "type": "object", + "description": "A structured chat content item containing plain text.", + "properties": { + "text": { + "type": "string", + "description": "The content of the message." + } + }, + "required": [ + "text" + ], + "allOf": [ + { + "$ref": "#/definitions/ChatMessageContentItem" + } + ], + "x-ms-discriminator-value": "text" + }, + "ChatRequestAssistantMessage": { + "type": "object", + "description": "A request chat message representing response or action from the assistant.", + "properties": { + "content": { + "type": "string", + "description": "The content of the message." + }, + "tool_calls": { + "type": "array", + "description": "The tool calls that must be resolved and have their outputs appended to subsequent input messages for the chat\ncompletions request to resolve as configured.", + "items": { + "$ref": "#/definitions/ChatCompletionsToolCall" + }, + "x-ms-client-name": "toolCalls" + }, + "audio": { + "$ref": "#/definitions/ChatRequestAudioReference", + "description": " The audio generated by a previous response in a multi-turn conversation." + } + }, + "allOf": [ + { + "$ref": "#/definitions/ChatRequestMessage" + } + ], + "x-ms-discriminator-value": "assistant" + }, + "ChatRequestAudioReference": { + "type": "object", + "description": "A reference to an audio response generated by the model.", + "properties": { + "id": { + "type": "string", + "description": " Unique identifier for the audio response. This value corresponds to the id of a previous audio completion." + } + }, + "required": [ + "id" + ] + }, + "ChatRequestMessage": { + "type": "object", + "description": "An abstract representation of a chat message as provided in a request.", + "properties": { + "role": { + "$ref": "#/definitions/ChatRole", + "description": "The chat role associated with this message." + } + }, + "discriminator": "role", + "required": [ + "role" + ] + }, + "ChatRequestSystemMessage": { + "type": "object", + "description": "A request chat message containing system instructions that influence how the model will generate a chat completions\nresponse.", + "properties": { + "content": { + "type": "string", + "description": "The contents of the system message." + } + }, + "required": [ + "content" + ], + "allOf": [ + { + "$ref": "#/definitions/ChatRequestMessage" + } + ], + "x-ms-discriminator-value": "system" + }, + "ChatRequestToolMessage": { + "type": "object", + "description": "A request chat message representing requested output from a configured tool.", + "properties": { + "content": { + "type": "string", + "description": "The content of the message." + }, + "tool_call_id": { + "type": "string", + "description": "The ID of the tool call resolved by the provided content.", + "x-ms-client-name": "toolCallId" + } + }, + "required": [ + "tool_call_id" + ], + "allOf": [ + { + "$ref": "#/definitions/ChatRequestMessage" + } + ], + "x-ms-discriminator-value": "tool" + }, + "ChatRequestUserMessage": { + "type": "object", + "description": "A request chat message representing user input to the assistant.", + "properties": { + "content": { + "description": "The contents of the user message, with available input types varying by selected model." + } + }, + "required": [ + "content" + ], + "allOf": [ + { + "$ref": "#/definitions/ChatRequestMessage" + } + ], + "x-ms-discriminator-value": "user" + }, + "ChatResponseMessage": { + "type": "object", + "description": "A representation of a chat message as received in a response.", + "properties": { + "role": { + "$ref": "#/definitions/ChatRole", + "description": "The chat role associated with the message." + }, + "content": { + "type": "string", + "description": "The content of the message.", + "x-nullable": true + }, + "tool_calls": { + "type": "array", + "description": "The tool calls that must be resolved and have their outputs appended to subsequent input messages for the chat\ncompletions request to resolve as configured.", + "items": { + "$ref": "#/definitions/ChatCompletionsToolCall" + }, + "x-ms-client-name": "toolCalls" + }, + "audio": { + "$ref": "#/definitions/ChatCompletionsAudio", + "description": " The audio generated by the model as a response to the messages if the model is configured to generate audio." + } + }, + "required": [ + "role", + "content" + ] + }, + "ChatRole": { + "type": "string", + "description": "A description of the intended purpose of a message within a chat completions interaction.", + "enum": [ + "system", + "developer", + "user", + "assistant", + "tool" + ], + "x-ms-enum": { + "name": "ChatRole", + "modelAsString": true, + "values": [ + { + "name": "system", + "value": "system", + "description": "The role that instructs or sets the behavior of the assistant." + }, + { + "name": "developer", + "value": "developer", + "description": "The role that provides instructions to the model prioritized ahead of user messages." + }, + { + "name": "user", + "value": "user", + "description": "The role that provides input for chat completions." + }, + { + "name": "assistant", + "value": "assistant", + "description": "The role that provides responses to system-instructed, user-prompted input." + }, + { + "name": "tool", + "value": "tool", + "description": "The role that represents extension tool activity within a chat completions operation." + } + ] + } + }, + "CompletionsFinishReason": { + "type": "string", + "description": "Representation of the manner in which a completions response concluded.", + "enum": [ + "stop", + "length", + "content_filter", + "tool_calls" + ], + "x-ms-enum": { + "name": "CompletionsFinishReason", + "modelAsString": true, + "values": [ + { + "name": "stopped", + "value": "stop", + "description": "Completions ended normally and reached its end of token generation." + }, + { + "name": "tokenLimitReached", + "value": "length", + "description": "Completions exhausted available token limits before generation could complete." + }, + { + "name": "contentFiltered", + "value": "content_filter", + "description": "Completions generated a response that was identified as potentially sensitive per content\nmoderation policies." + }, + { + "name": "toolCalls", + "value": "tool_calls", + "description": "Completion ended with the model calling a provided tool for output." + } + ] + } + }, + "CompletionsUsage": { + "type": "object", + "description": "Representation of the token counts processed for a completions request.\nCounts consider all tokens across prompts, choices, choice alternates, best_of generations, and\nother consumers.", + "properties": { + "completion_tokens": { + "type": "integer", + "format": "int32", + "description": "The number of tokens generated across all completions emissions." + }, + "prompt_tokens": { + "type": "integer", + "format": "int32", + "description": "The number of tokens in the provided prompts for the completions request." + }, + "total_tokens": { + "type": "integer", + "format": "int32", + "description": "The total number of tokens processed for the completions request and response." + }, + "completion_tokens_details": { + "$ref": "#/definitions/CompletionsUsageDetails", + "description": "Breakdown of tokens used in a completion." + }, + "prompt_tokens_details": { + "$ref": "#/definitions/PromptUsageDetails", + "description": "Breakdown of tokens used in the prompt/chat history." + } + }, + "required": [ + "completion_tokens", + "prompt_tokens", + "total_tokens" + ] + }, + "CompletionsUsageDetails": { + "type": "object", + "description": "A breakdown of tokens used in a completion.", + "properties": { + "audio_tokens": { + "type": "integer", + "format": "int32", + "description": "The number of tokens corresponding to audio input." + }, + "total_tokens": { + "type": "integer", + "format": "int32", + "description": "The total number of tokens processed for the completions request and response." + } + }, + "required": [ + "audio_tokens", + "total_tokens" + ] + }, + "EmbeddingEncodingFormat": { + "type": "string", + "description": "Specifies the types of embeddings to generate. Compressed embeddings types like `uint8`, `int8`, `ubinary` and \n`binary`, may reduce storage costs without sacrificing the integrity of the data. Returns a 422 error if the\nmodel doesn't support the value or parameter. Read the model's documentation to know the values supported by\nthe your model.", + "enum": [ + "base64", + "binary", + "float", + "int8", + "ubinary", + "uint8" + ], + "x-ms-enum": { + "name": "EmbeddingEncodingFormat", + "modelAsString": true, + "values": [ + { + "name": "base64", + "value": "base64", + "description": "Get back binary representation of the embeddings encoded as Base64 string. OpenAI Python library retrieves \nembeddings from the API as encoded binary data, rather than using intermediate decimal representations as is \nusually done." + }, + { + "name": "binary", + "value": "binary", + "description": "Get back signed binary embeddings" + }, + { + "name": "float", + "value": "float", + "description": "Get back full precision embeddings" + }, + { + "name": "int8", + "value": "int8", + "description": "Get back signed int8 embeddings" + }, + { + "name": "ubinary", + "value": "ubinary", + "description": "Get back unsigned binary embeddings" + }, + { + "name": "uint8", + "value": "uint8", + "description": "Get back unsigned int8 embeddings" + } + ] + } + }, + "EmbeddingInputType": { + "type": "string", + "description": "Represents the input types used for embedding search.", + "enum": [ + "text", + "query", + "document" + ], + "x-ms-enum": { + "name": "EmbeddingInputType", + "modelAsString": true, + "values": [ + { + "name": "text", + "value": "text", + "description": "Indicates the input is a general text input." + }, + { + "name": "query", + "value": "query", + "description": "Indicates the input represents a search query to find the most relevant documents in your vector database." + }, + { + "name": "document", + "value": "document", + "description": "Indicates the input represents a document that is stored in a vector database." + } + ] + } + }, + "EmbeddingItem": { + "type": "object", + "description": "Representation of a single embeddings relatedness comparison.", + "properties": { + "embedding": { + "type": "array", + "description": "List of embedding values for the input prompt. These represent a measurement of the\nvector-based relatedness of the provided input. Or a base64 encoded string of the embedding vector.", + "items": { + "type": "number", + "format": "float" + } + }, + "index": { + "type": "integer", + "format": "int32", + "description": "Index of the prompt to which the EmbeddingItem corresponds." + }, + "object": { + "type": "string", + "description": "The object type of this embeddings item. Will always be `embedding`.", + "enum": [ + "embedding" + ], + "x-ms-enum": { + "modelAsString": false + } + } + }, + "required": [ + "embedding", + "index", + "object" + ] + }, + "EmbeddingsOptions": { + "type": "object", + "description": "The configuration information for an embeddings request.", + "properties": { + "input": { + "type": "array", + "description": "Input text to embed, encoded as a string or array of tokens.\nTo embed multiple inputs in a single request, pass an array\nof strings or array of token arrays.", + "items": { + "type": "string" + } + }, + "dimensions": { + "type": "integer", + "format": "int32", + "description": "Optional. The number of dimensions the resulting output embeddings should have.\nPassing null causes the model to use its default value.\nReturns a 422 error if the model doesn't support the value or parameter." + }, + "encoding_format": { + "$ref": "#/definitions/EmbeddingEncodingFormat", + "description": "Optional. The desired format for the returned embeddings." + }, + "input_type": { + "$ref": "#/definitions/EmbeddingInputType", + "description": "Optional. The type of the input.\nReturns a 422 error if the model doesn't support the value or parameter." + }, + "model": { + "type": "string", + "description": "ID of the specific AI model to use, if more than one model is available on the endpoint." + } + }, + "required": [ + "input" + ], + "additionalProperties": {} + }, + "EmbeddingsResult": { + "type": "object", + "description": "Representation of the response data from an embeddings request.\nEmbeddings measure the relatedness of text strings and are commonly used for search, clustering,\nrecommendations, and other similar scenarios.", + "properties": { + "id": { + "type": "string", + "description": "Unique identifier for the embeddings result." + }, + "data": { + "type": "array", + "description": "Embedding values for the prompts submitted in the request.", + "items": { + "$ref": "#/definitions/EmbeddingItem" + }, + "x-ms-identifiers": [] + }, + "usage": { + "$ref": "#/definitions/EmbeddingsUsage", + "description": "Usage counts for tokens input using the embeddings API." + }, + "object": { + "type": "string", + "description": "The object type of the embeddings result. Will always be `list`.", + "enum": [ + "list" + ], + "x-ms-enum": { + "modelAsString": false + } + }, + "model": { + "type": "string", + "description": "The model ID used to generate this result." + } + }, + "required": [ + "id", + "data", + "usage", + "object", + "model" + ] + }, + "EmbeddingsUsage": { + "type": "object", + "description": "Measurement of the amount of tokens used in this request and response.", + "properties": { + "prompt_tokens": { + "type": "integer", + "format": "int32", + "description": "Number of tokens in the request." + }, + "total_tokens": { + "type": "integer", + "format": "int32", + "description": "Total number of tokens transacted in this request/response. Should equal the\nnumber of tokens in the request." + } + }, + "required": [ + "prompt_tokens", + "total_tokens" + ] + }, + "ExtraParameters": { + "type": "string", + "description": "Controls what happens if extra parameters, undefined by the REST API, are passed in the JSON request payload.", + "enum": [ + "error", + "drop", + "pass-through" + ], + "x-ms-enum": { + "name": "ExtraParameters", + "modelAsString": true, + "values": [ + { + "name": "error", + "value": "error", + "description": "The service will error if it detected extra parameters in the request payload. This is the service default." + }, + { + "name": "drop", + "value": "drop", + "description": "The service will ignore (drop) extra parameters in the request payload. It will only pass the known parameters to the back-end AI model." + }, + { + "name": "pass_through", + "value": "pass-through", + "description": "The service will pass extra parameters to the back-end AI model." + } + ] + } + }, + "FunctionCall": { + "type": "object", + "description": "The name and arguments of a function that should be called, as generated by the model.", + "properties": { + "name": { + "type": "string", + "description": "The name of the function to call." + }, + "arguments": { + "type": "string", + "description": "The arguments to call the function with, as generated by the model in JSON format.\nNote that the model does not always generate valid JSON, and may hallucinate parameters\nnot defined by your function schema. Validate the arguments in your code before calling\nyour function." + } + }, + "required": [ + "name", + "arguments" + ] + }, + "FunctionDefinition": { + "type": "object", + "description": "The definition of a caller-specified function that chat completions may invoke in response to matching user input.", + "properties": { + "name": { + "type": "string", + "description": "The name of the function to be called." + }, + "description": { + "type": "string", + "description": "A description of what the function does. The model will use this description when selecting the function and\ninterpreting its parameters." + }, + "parameters": { + "type": "object", + "description": "The parameters the function accepts, described as a JSON Schema object.", + "additionalProperties": {} + } + }, + "required": [ + "name" + ] + }, + "ImageEmbeddingInput": { + "type": "object", + "description": "Represents an image with optional text.", + "properties": { + "image": { + "type": "string", + "description": "The input image encoded in base64 string as a data URL. Example: `data:image/{format};base64,{data}`." + }, + "text": { + "type": "string", + "description": "Optional. The text input to feed into the model (like DINO, CLIP).\nReturns a 422 error if the model doesn't support the value or parameter." + } + }, + "required": [ + "image" + ] + }, + "ImageEmbeddingsOptions": { + "type": "object", + "description": "The configuration information for an image embeddings request.", + "properties": { + "input": { + "type": "array", + "description": "Input image to embed. To embed multiple inputs in a single request, pass an array.\nThe input must not exceed the max input tokens for the model.", + "items": { + "$ref": "#/definitions/ImageEmbeddingInput" + }, + "x-ms-identifiers": [] + }, + "dimensions": { + "type": "integer", + "format": "int32", + "description": "Optional. The number of dimensions the resulting output embeddings should have.\nPassing null causes the model to use its default value.\nReturns a 422 error if the model doesn't support the value or parameter." + }, + "encoding_format": { + "$ref": "#/definitions/EmbeddingEncodingFormat", + "description": "Optional. The number of dimensions the resulting output embeddings should have.\nPassing null causes the model to use its default value.\nReturns a 422 error if the model doesn't support the value or parameter." + }, + "input_type": { + "$ref": "#/definitions/EmbeddingInputType", + "description": "Optional. The type of the input.\nReturns a 422 error if the model doesn't support the value or parameter." + }, + "model": { + "type": "string", + "description": "ID of the specific AI model to use, if more than one model is available on the endpoint." + } + }, + "required": [ + "input" + ], + "additionalProperties": {} + }, + "ModelInfo": { + "type": "object", + "description": "Represents some basic information about the AI model.", + "properties": { + "model_name": { + "type": "string", + "description": "The name of the AI model. For example: `Phi21`" + }, + "model_type": { + "$ref": "#/definitions/ModelType", + "description": "The type of the AI model. A Unique identifier for the profile." + }, + "model_provider_name": { + "type": "string", + "description": "The model provider name. For example: `Microsoft Research`" + } + }, + "required": [ + "model_name", + "model_type", + "model_provider_name" + ] + }, + "ModelType": { + "type": "string", + "description": "The type of AI model", + "enum": [ + "embeddings", + "chat-completion" + ], + "x-ms-enum": { + "name": "ModelType", + "modelAsString": true, + "values": [ + { + "name": "embeddings", + "value": "embeddings", + "description": "A model capable of generating embeddings from a text" + }, + { + "name": "chat_completion", + "value": "chat-completion", + "description": "A model capable of taking chat-formatted messages and generate responses" + } + ] + } + }, + "PromptUsageDetails": { + "type": "object", + "description": "A breakdown of tokens used in the prompt/chat history.", + "properties": { + "audio_tokens": { + "type": "integer", + "format": "int32", + "description": "The number of tokens corresponding to audio input." + }, + "cached_tokens": { + "type": "integer", + "format": "int32", + "description": "The total number of tokens cached." + } + }, + "required": [ + "audio_tokens", + "cached_tokens" + ] + }, + "StreamingChatChoiceUpdate": { + "type": "object", + "description": "Represents an update to a single prompt completion when the service is streaming updates \nusing Server Sent Events (SSE).\nGenerally, `n` choices are generated per provided prompt with a default value of 1.\nToken limits and other settings may limit the number of choices generated.", + "properties": { + "index": { + "type": "integer", + "format": "int32", + "description": "The ordered index associated with this chat completions choice." + }, + "finish_reason": { + "$ref": "#/definitions/CompletionsFinishReason", + "description": "The reason that this chat completions choice completed its generated.", + "x-nullable": true + }, + "delta": { + "$ref": "#/definitions/StreamingChatResponseMessageUpdate", + "description": "An update to the chat message for a given chat completions prompt." + } + }, + "required": [ + "index", + "finish_reason", + "delta" + ] + }, + "StreamingChatCompletionsUpdate": { + "type": "object", + "description": "Represents a response update to a chat completions request, when the service is streaming updates \nusing Server Sent Events (SSE).\nCompletions support a wide variety of tasks and generate text that continues from or \"completes\"\nprovided prompt data.", + "properties": { + "id": { + "type": "string", + "description": "A unique identifier associated with this chat completions response." + }, + "object": { + "type": "string", + "description": "The response object type, which is always `chat.completion`.", + "enum": [ + "chat.completion" + ], + "x-ms-enum": { + "modelAsString": false + } + }, + "created": { + "type": "integer", + "format": "unixtime", + "description": "The first timestamp associated with generation activity for this completions response,\nrepresented as seconds since the beginning of the Unix epoch of 00:00 on 1 Jan 1970." + }, + "model": { + "type": "string", + "description": "The model used for the chat completion." + }, + "choices": { + "type": "array", + "description": "An update to the collection of completion choices associated with this completions response.\nGenerally, `n` choices are generated per provided prompt with a default value of 1.\nToken limits and other settings may limit the number of choices generated.", + "minItems": 1, + "items": { + "$ref": "#/definitions/StreamingChatChoiceUpdate" + }, + "x-ms-identifiers": [] + }, + "usage": { + "$ref": "#/definitions/CompletionsUsage", + "description": "Usage information for tokens processed and generated as part of this completions operation." + } + }, + "required": [ + "id", + "object", + "created", + "model", + "choices" + ] + }, + "StreamingChatResponseMessageUpdate": { + "type": "object", + "description": "A representation of a chat message update as received in a streaming response.", + "properties": { + "role": { + "$ref": "#/definitions/ChatRole", + "description": "The chat role associated with the message. If present, should always be 'assistant'" + }, + "content": { + "type": "string", + "description": "The content of the message." + }, + "tool_calls": { + "type": "array", + "description": "The tool calls that must be resolved and have their outputs appended to subsequent input messages for the chat\ncompletions request to resolve as configured.", + "items": { + "$ref": "#/definitions/StreamingChatResponseToolCallUpdate" + }, + "x-ms-client-name": "toolCalls" + } + } + }, + "StreamingChatResponseToolCallUpdate": { + "type": "object", + "description": "An update to the function tool call information requested by the AI model.", + "properties": { + "id": { + "type": "string", + "description": "The ID of the tool call." + }, + "function": { + "$ref": "#/definitions/FunctionCall", + "description": "Updates to the function call requested by the AI model." + } + }, + "required": [ + "id", + "function" + ] + } + }, + "parameters": { + } +} diff --git a/labs/openai-agents-tf/locals.tf b/labs/openai-agents-tf/locals.tf new file mode 100644 index 0000000..6ccd69d --- /dev/null +++ b/labs/openai-agents-tf/locals.tf @@ -0,0 +1,15 @@ +locals { + location = var.location != null ? var.location : azurerm_resource_group.rg.location + resource_suffix = random_string.suffix.result + apim_logger_name = "apim-logger-${local.resource_suffix}" + log_settings = { + headers = ["Content-type", "User-agent", "x-ms-region", "x-ratelimit-remaining-tokens", "x-ratelimit-remaining-requests"] + body = { bytes = 8191 } + } + callback_url = azapi_resource_action.logic_app_callback.output["value"] + base_path = regex("^(https://[^?]+/triggers)(/|$)", local.callback_url)[0] + sig = regex("sig=([^&]+)", local.callback_url)[0] + api-version = "2016-10-01" + sp = regex("sp=([^&]+)", local.callback_url)[0] + sv = regex("sv=([^&]+)", local.callback_url)[0] +} \ No newline at end of file diff --git a/labs/openai-agents-tf/main.tf b/labs/openai-agents-tf/main.tf new file mode 100644 index 0000000..e8c2548 --- /dev/null +++ b/labs/openai-agents-tf/main.tf @@ -0,0 +1,533 @@ +resource "random_string" "suffix" { + length = 10 + special = false + upper = false +} +resource "azurerm_resource_group" "rg" { + name = var.resource_group_name + location = var.resource_group_location +} +#### Log Analytics Workspace +resource "azurerm_log_analytics_workspace" "law" { + name = "law-${local.resource_suffix}" + location = local.location + resource_group_name = azurerm_resource_group.rg.name + sku = "PerGB2018" + retention_in_days = 30 +} +#### Application Insights +resource "azurerm_application_insights" "appinsights" { + name = "appinsights-${local.resource_suffix}" + location = local.location + resource_group_name = azurerm_resource_group.rg.name + application_type = "web" + workspace_id = azurerm_log_analytics_workspace.law.id +} +#### API Management +resource "azapi_resource" "apim" { + type = "Microsoft.ApiManagement/service@2024-06-01-preview" + name = "${var.apim_resource_name}-${local.resource_suffix}" + parent_id = azurerm_resource_group.rg.id + location = local.location + schema_validation_enabled = true + + identity { + type = "SystemAssigned" + } + + body = { + sku = { + name = var.apim_sku + capacity = 1 + } + properties = { + publisherEmail = "noreply@microsoft.com" + publisherName = "Microsoft" + virtualNetworkType = "None" + publicNetworkAccess = "Enabled" + } + } + + response_export_values = ["*"] +} +#### Create APIM Logger +resource "azurerm_api_management_logger" "apim_logger" { + name = local.apim_logger_name + api_management_name = azapi_resource.apim.name + resource_group_name = azurerm_resource_group.rg.name + + application_insights { + instrumentation_key = azurerm_application_insights.appinsights.instrumentation_key + } +} +#### Cognitive Services (OpenAI) +resource "azurerm_cognitive_account" "openai" { + for_each = { for idx, val in var.openai_config : idx => val } + + location = each.value.location + resource_group_name = azurerm_resource_group.rg.name + sku_name = var.openai_sku + outbound_network_access_restricted = false + public_network_access_enabled = true + kind = "OpenAI" + custom_subdomain_name = "${lower(each.value.name)}-${local.resource_suffix}" + name = "${each.value.name}-${local.resource_suffix}" +} +resource "azurerm_cognitive_deployment" "deployment" { + for_each = { for idx, val in var.openai_config : idx => val } + + name = var.openai_deployment_name + cognitive_account_id = azurerm_cognitive_account.openai[each.key].id + + sku { + name = "GlobalStandard" // "GlobalStandard" // "Standard" // DataZoneStandard, GlobalBatch, GlobalStandard and ProvisionedManaged + capacity = var.openai_model_capacity + } + + model { + format = "OpenAI" + name = var.openai_model_name + version = var.openai_model_version + } +} +resource "azurerm_monitor_diagnostic_setting" "openai" { + for_each = { for idx, val in var.openai_config : idx => val } + + name = "${lower(each.value.name)}-openai-diag" + target_resource_id = azurerm_cognitive_account.openai[each.key].id + log_analytics_workspace_id = azurerm_log_analytics_workspace.law.id + + metric { + category = "AllMetrics" + enabled = true + } + +} +# Grant APIM identity access to OpenAI +resource "azurerm_role_assignment" "openai_user" { + for_each = { for idx, val in var.openai_config : idx => val } + scope = azurerm_cognitive_account.openai[each.key].id + role_definition_name = "Cognitive Services OpenAI User" + principal_id = azapi_resource.apim.identity.0.principal_id +} +#### Logic App Workflow for Order Processing +resource "azapi_resource" "place_order_workflow" { + type = "Microsoft.Logic/workflows@2016-06-01" + name = "place_order_workflow" + location = "West US" + parent_id = azurerm_resource_group.rg.id # Replace with your actual resource group or parent resource ID + body = { + properties = { + state = "Enabled" + definition = { + "$schema" = "https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#" + contentVersion = "1.0.0.0" + parameters = { + "$connections" = { + defaultValue = {} + type = "Object" + } + } + triggers = { + PlaceOrder = { + type = "Request" + kind = "Http" + inputs = { + schema = { + type = "object" + properties = { + sku = { + type = "string" + } + quantity = { + type = "integer" + } + } + } + } + description = "Place an Order to the specified sku and quantity." + } + } + actions = { + Condition = { + actions = { + UpdateStatusOk = { + type = "Response" + kind = "Http" + inputs = { + statusCode = 200 + body = { + status = "@concat('Order placed with id ', rand(1000,9000), ' for SKU ', triggerBody()?['sku'], ' with ', triggerBody()?['quantity'], ' items.')" + } + } + description = "Return the status for the order." + } + } + runAfter = {} + else = { + actions = { + UpdateStatusError = { + type = "Response" + kind = "Http" + inputs = { + statusCode = 200 + body = { + status = "The order was not placed because the quantity exceeds the maximum limit of five items." + } + } + description = "Return the status for the order." + } + } + } + expression = { + and = [ + { + lessOrEquals = [ + "@triggerBody()?['quantity']", + 5 + ] + } + ] + } + type = "If" + } + } + outputs = {} + } + } + } +} +#### APIM APIs +resource "azurerm_api_management_api" "weather_api" { + name = "weather-api" + resource_group_name = azurerm_resource_group.rg.name + api_management_name = azapi_resource.apim.name + revision = "1" + display_name = "City Weather API" + path = var.weather_api_path + protocols = ["https"] + import { + content_format = "openapi+json" + content_value = file("${path.module}/city-weather-openapi.json") + } + subscription_key_parameter_names { + header = "api-key" + query = "api-key" + } +} +resource "azurerm_api_management_api" "place_order_api" { + name = "place-order-api" + resource_group_name = azurerm_resource_group.rg.name + api_management_name = azapi_resource.apim.name + revision = "1" + display_name = "Place Order API" + path = var.place_order_api_path + protocols = ["https"] + import { + content_format = "openapi+json" + content_value = file("place-order-openapi.json") + } + subscription_key_parameter_names { + header = "api-key" + query = "api-key" + } +} +resource "azurerm_api_management_api" "product_catalog_api" { + name = "product-catalog-api" + resource_group_name = azurerm_resource_group.rg.name + api_management_name = azapi_resource.apim.name + revision = "1" + display_name = "Product Catalog API" + path = var.product_catalog_api_path + protocols = ["https"] + import { + content_format = "openapi+json" + content_value = file("product-catalog-openapi.json") + } + subscription_key_parameter_names { + header = "api-key" + query = "api-key" + } +} +resource "azurerm_api_management_api" "apim-api-openai" { + name = "apim-ai-gateway" + resource_group_name = azurerm_resource_group.rg.name + api_management_name = azapi_resource.apim.name + revision = "1" + description = "Azure OpenAI APIs for completions and search" + display_name = "OpenAI" + path = "openai" + protocols = ["https"] + service_url = null + subscription_required = false + api_type = "http" + + import { + content_format = "openapi-link" + content_value = var.openai_api_spec_url + } + + subscription_key_parameter_names { + header = "api-key" + query = "api-key" + } +} +#### APIM Backend Pool +resource "azurerm_api_management_backend" "apim-backend-openai" { + for_each = var.openai_config + + name = each.value.name + resource_group_name = azurerm_resource_group.rg.name + api_management_name = azapi_resource.apim.name + protocol = "http" + url = "${azurerm_cognitive_account.openai[each.key].endpoint}openai" +} +resource "azapi_update_resource" "apim-backend-circuit-breaker" { + for_each = var.openai_config + + type = "Microsoft.ApiManagement/service/backends@2023-09-01-preview" + resource_id = azurerm_api_management_backend.apim-backend-openai[each.key].id + + body = { + properties = { + circuitBreaker = { + rules = [ + { + failureCondition = { + count = 1 + errorReasons = [ + "Server errors" + ] + interval = "PT5M" + statusCodeRanges = [ + { + min = 429 + max = 429 + } + ] + } + name = "openAIBreakerRule" + tripDuration = "PT1M" + acceptRetryAfter = true // respects the Retry-After header + } + ] + } + } + } +} +resource "azapi_resource" "apim-backend-pool-openai" { + type = "Microsoft.ApiManagement/service/backends@2023-09-01-preview" + name = "apim-backend-pool" + parent_id = azapi_resource.apim.id + schema_validation_enabled = false + + body = { + properties = { + type = "Pool" + pool = { + services = [ + for k, v in var.openai_config : + { + id = azurerm_api_management_backend.apim-backend-openai[k].id + } + ] + } + } + } +} +resource "azapi_resource_action" "place_order_callback" { + type = "Microsoft.Logic/workflows/triggers/listCallbackUrl@2016-06-01" + resource_id = "${azapi_resource.place_order_workflow.id}/triggers/PlaceOrder/listCallbackUrl" + + response_export_values = ["value"] +} +resource "azurerm_api_management_backend" "backend_place_order_api" { + name = "orderworkflow-backend" + resource_group_name = azurerm_resource_group.rg.name + api_management_name = azapi_resource.apim.name + url = local.base_path + protocol = "http" + + credentials { + query = { + sig = local.sig + api-version = local.api-version + sp = local.sp + sv = local.sv + } + } + + depends_on = [azapi_resource_action.place_order_callback] +} +#### APIM API Policies +resource "azurerm_api_management_api_policy" "apim-openai-policy-openai" { + api_name = azurerm_api_management_api.apim-api-openai.name + api_management_name = azurerm_api_management_api.apim-api-openai.api_management_name + resource_group_name = azurerm_api_management_api.apim-api-openai.resource_group_name + + xml_content = replace(file("policy.xml"), "{backend-id}", azapi_resource.apim-backend-pool-openai.name) +} +resource "azurerm_api_management_api_policy" "weather_api_policy" { + api_name = azurerm_api_management_api.weather_api.name + resource_group_name = azurerm_resource_group.rg.name + api_management_name = azapi_resource.apim.name + xml_content = file("city-weather-mock-policy.xml") +} +resource "azurerm_api_management_api_policy" "place_order_api_policy" { + api_name = azurerm_api_management_api.place_order_api.name + resource_group_name = azurerm_resource_group.rg.name + api_management_name = azapi_resource.apim.name + xml_content = file("place-order-policy.xml") + depends_on = [azurerm_api_management_backend.backend_place_order_api] +} +resource "azurerm_api_management_api_policy" "product_catalog_api_policy" { + api_name = azurerm_api_management_api.product_catalog_api.name + resource_group_name = azurerm_resource_group.rg.name + api_management_name = azapi_resource.apim.name + xml_content = file("product-catalog-mock-policy.xml") +} +#### APIM API Diagnostics +resource "azurerm_api_management_api_diagnostic" "weather_api_diagnostics" { + + identifier = "applicationinsights" + api_name = azurerm_api_management_api.weather_api.name + resource_group_name = azurerm_resource_group.rg.name + api_management_name = azapi_resource.apim.name + api_management_logger_id = azurerm_api_management_logger.apim_logger.id + log_client_ip = true + sampling_percentage = 100 + always_log_errors = true + verbosity = "verbose" + http_correlation_protocol = "W3C" + frontend_request { + headers_to_log = local.log_settings.headers + body_bytes = local.log_settings.body.bytes + } + + backend_request { + headers_to_log = local.log_settings.headers + body_bytes = local.log_settings.body.bytes + } + + frontend_response { + headers_to_log = local.log_settings.headers + body_bytes = local.log_settings.body.bytes + } + + backend_response { + headers_to_log = local.log_settings.headers + body_bytes = local.log_settings.body.bytes + } +} +resource "azurerm_api_management_api_diagnostic" "place_order_api_diagnostics" { + identifier = "applicationinsights" + api_name = azurerm_api_management_api.place_order_api.name + resource_group_name = azurerm_resource_group.rg.name + api_management_name = azapi_resource.apim.name + api_management_logger_id = azurerm_api_management_logger.apim_logger.id + log_client_ip = true + sampling_percentage = 100 + always_log_errors = true + verbosity = "verbose" + http_correlation_protocol = "W3C" + frontend_request { + headers_to_log = local.log_settings.headers + body_bytes = local.log_settings.body.bytes + } + + backend_request { + headers_to_log = local.log_settings.headers + body_bytes = local.log_settings.body.bytes + } + + frontend_response { + headers_to_log = local.log_settings.headers + body_bytes = local.log_settings.body.bytes + } + + backend_response { + headers_to_log = local.log_settings.headers + body_bytes = local.log_settings.body.bytes + } +} +resource "azurerm_api_management_api_diagnostic" "product_catalog_api_diagnostics" { + identifier = "applicationinsights" + api_name = azurerm_api_management_api.product_catalog_api.name + resource_group_name = azurerm_resource_group.rg.name + api_management_name = azapi_resource.apim.name + api_management_logger_id = azurerm_api_management_logger.apim_logger.id + log_client_ip = true + sampling_percentage = 100 + always_log_errors = true + verbosity = "verbose" + http_correlation_protocol = "W3C" + frontend_request { + headers_to_log = local.log_settings.headers + body_bytes = local.log_settings.body.bytes + } + + backend_request { + headers_to_log = local.log_settings.headers + body_bytes = local.log_settings.body.bytes + } + + frontend_response { + headers_to_log = local.log_settings.headers + body_bytes = local.log_settings.body.bytes + } + + backend_response { + headers_to_log = local.log_settings.headers + body_bytes = local.log_settings.body.bytes + } + + +} +resource "azurerm_api_management_api_diagnostic" "openai_api_diagnostics" { + identifier = "applicationinsights" + api_name = azurerm_api_management_api.apim-api-openai.name + resource_group_name = azurerm_resource_group.rg.name + api_management_name = azapi_resource.apim.name + api_management_logger_id = azurerm_api_management_logger.apim_logger.id + log_client_ip = true + sampling_percentage = 100 + always_log_errors = true + verbosity = "verbose" + http_correlation_protocol = "W3C" + frontend_request { + headers_to_log = local.log_settings.headers + body_bytes = local.log_settings.body.bytes + } + + backend_request { + headers_to_log = local.log_settings.headers + body_bytes = local.log_settings.body.bytes + } + + frontend_response { + headers_to_log = local.log_settings.headers + body_bytes = local.log_settings.body.bytes + } + + backend_response { + headers_to_log = local.log_settings.headers + body_bytes = local.log_settings.body.bytes + } + + +} +#### APIM API Subscriptions +resource "azurerm_api_management_subscription" "openai-subscription" { + display_name = "openai-subscription" + api_management_name = azapi_resource.apim.name + resource_group_name = azurerm_resource_group.rg.name + allow_tracing = true + state = "active" +} +resource "azurerm_api_management_subscription" "tools-apis-subscription" { + display_name = "Tools APIs Subscription" + api_management_name = azapi_resource.apim.name + resource_group_name = azurerm_resource_group.rg.name + allow_tracing = true + state = "active" +} \ No newline at end of file diff --git a/labs/openai-agents-tf/openai-agents-tf.ipynb b/labs/openai-agents-tf/openai-agents-tf.ipynb new file mode 100644 index 0000000..9666903 --- /dev/null +++ b/labs/openai-agents-tf/openai-agents-tf.ipynb @@ -0,0 +1,524 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# APIM ❤️ AI Agents\n", + "\n", + "## OpenAI Agents Lab (with terraform)\n", + "\n", + "![flow](../../images/openai-agents.gif)\n", + "\n", + "Playground to try the [OpenAI Agents](https://openai.github.io/openai-agents-python/) with Azure OpenAI models and API based tools through Azure API Management. This enables limitless opportunities for AI agents while maintaining control through Azure API Management!\n", + "\n", + "### Prerequisites\n", + "\n", + "- [Python 3.12 or later version](https://www.python.org/) installed\n", + "- [VS Code](https://code.visualstudio.com/) installed with the [Jupyter notebook extension](https://marketplace.visualstudio.com/items?itemName=ms-toolsai.jupyter) enabled\n", + "- [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", + "- [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", + "- [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", + "- [Terraform CLI](https://learn.hashicorp.com/tutorials/terraform/install-cli) installed\n", + "\n", + "▶️ Click `Run All` to execute all steps sequentially, or execute them `Step by Step`...\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "### 0️⃣ Initialize notebook variables\n", + "\n", + "- Resources will be suffixed by a unique string based on your subscription id.\n", + "- 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", + "- Adjust the OpenAI model and version according the [availability by region.](https://learn.microsoft.com/azure/ai-services/openai/concepts/models) " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "metadata": {} + }, + "outputs": [], + "source": [ + "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 = \"uksouth\"\n", + "\n", + "apim_sku = 'Basicv2'\n", + "\n", + "openai_resources = \"\"\"{\n", + " openai-1 = {\n", + " name = \"openai1\",\n", + " location = \"uksouth\",\n", + " }\n", + " }\n", + "\"\"\"\n", + "\n", + "openai_model_name = \"gpt-4o-mini\"\n", + "openai_model_version = \"2024-07-18\"\n", + "openai_model_capacity = 8\n", + "openai_deployment_name = \"gpt-4o-mini\"\n", + "openai_api_version = \"2024-10-21\"\n", + "\n", + "utils.print_ok('Notebook initialized')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "### 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." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "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}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "### 2️⃣ Create deployment using Terraform\n", + "\n", + "This lab uses [Terraform](https://learn.microsoft.com/en-us/azure/developer/terraform/overview) to declarative define all the resources that will be deployed in the specified resource group. Change the variables or the [main.tf](main.tf) directly to try different configurations." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Define the Terraform parameters\n", + "terraform_variables = f\"\"\"\n", + "resource_group_name = \"{resource_group_name}\"\n", + "resource_group_location = \"{resource_group_location}\"\n", + "apim_sku = \"{apim_sku}\"\n", + "openai_deployment_name = \"{openai_deployment_name}\"\n", + "openai_model_name = \"{openai_model_name}\"\n", + "openai_model_version = \"{openai_model_version}\"\n", + "openai_model_capacity = \"{openai_model_capacity}\"\n", + "openai_api_version = \"{openai_api_version}\"\n", + "openai_config = {openai_resources}\n", + "\"\"\"\n", + "\n", + "# Write the variables to the terraform.tfvars file\n", + "with open(\"terraform.tfvars\", \"w\") as terraform_variables_file:\n", + " terraform_variables_file.write(terraform_variables)\n", + "\n", + "utils.print_ok('terraform.tfvars file created')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The terraform commands `terraform init` and `terraform apply` will be executed in order to deploy the resources." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Intialize terraform\n", + "output = utils.run(\n", + " f\"terraform init\",\n", + " f\"Initialization succeeded\",\n", + " f\"Initialization failed\",\n", + ")\n", + "\n", + "# Specify the target subscription for Terraform\n", + "os.environ['ARM_SUBSCRIPTION_ID'] = subscription_id\n", + "\n", + "# Run the deployment\n", + "output = utils.run(\n", + " f\"terraform apply -auto-approve -var-file=terraform.tfvars\",\n", + " f\"Deployment succeeded\",\n", + " f\"Deployment failed\",\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "### 3️⃣ Get the deployment outputs\n", + "\n", + "Retrieve the required outputs from the Terraform deployment." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "app_insights_instrumentation_key = utils.run(\"terraform output application_insights_instrumentation_key\").json_data\n", + "apim_resource_gateway_url = utils.run(\"terraform output api_management_gateway_url\").json_data\n", + "openai_subscription_key = utils.run(\"terraform output openai_subscription_key\").json_data\n", + "tools_subscription_key = utils.run(\"terraform output tools_subscription_key\").json_data\n", + "\n", + "utils.print_info(f\"Application Insights Instrumentation Key: {app_insights_instrumentation_key}\")\n", + "utils.print_info(f\"APIM API Gateway URL: {apim_resource_gateway_url}\")\n", + "utils.print_info(f\"OpenAI Subscription Key: {'*' * len(openai_subscription_key)}\")\n", + "utils.print_info(f\"Tools Subscription Key: {'*' * len(tools_subscription_key)}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### ⚙️ Install OpenAI Agents SDK" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "! pip install openai-agents" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "### 🧪 Test the API using the OpenAI SDK\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from openai import AzureOpenAI\n", + "\n", + "client = AzureOpenAI(\n", + " azure_endpoint=apim_resource_gateway_url,\n", + " api_key=openai_subscription_key,\n", + " api_version=openai_api_version\n", + ")\n", + "response = client.chat.completions.create(model=openai_model_name, messages=[\n", + " {\"role\": \"system\", \"content\": \"You are a sarcastic, unhelpful assistant.\"},\n", + " {\"role\": \"user\", \"content\": \"Can you tell me the time, please?\"}\n", + "])\n", + "print(\"💬 \",response.choices[0].message.content)\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "### 🧪 Basic test with the Agents SDK\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from openai import AsyncAzureOpenAI\n", + "from agents import Agent, Runner, set_default_openai_client, set_default_openai_api, set_tracing_disabled\n", + "import nest_asyncio\n", + "nest_asyncio.apply()\n", + "\n", + "client = AsyncAzureOpenAI(azure_endpoint=apim_resource_gateway_url,\n", + " api_key=openai_subscription_key,\n", + " api_version=openai_api_version)\n", + "set_default_openai_client(client)\n", + "set_default_openai_api(\"chat_completions\")\n", + "agent = Agent(name=\"Sarcastic Assistant\", instructions=\"You are a sarcastic, unhelpful assistant.\", model=openai_deployment_name)\n", + "\n", + "result = Runner.run_sync(agent, \"Can you tell me the time, please?\")\n", + "print(\"💬\", result.final_output)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "### 🧪 Handoffs example\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from agents import Agent, Runner\n", + "import asyncio\n", + "\n", + "spanish_agent = Agent(\n", + " name=\"Spanish agent\",\n", + " instructions=\"You only speak Spanish.\",\n", + " model=openai_deployment_name\n", + ")\n", + "\n", + "english_agent = Agent(\n", + " name=\"English agent\",\n", + " instructions=\"You only speak English\",\n", + " model=openai_deployment_name\n", + ")\n", + "\n", + "triage_agent = Agent(\n", + " name=\"Triage agent\",\n", + " instructions=\"Handoff to the appropriate agent based on the language of the request.\",\n", + " handoffs=[spanish_agent, english_agent],\n", + " model=openai_deployment_name\n", + ")\n", + "\n", + "async def main():\n", + " client = AsyncAzureOpenAI(azure_endpoint=apim_resource_gateway_url,\n", + " api_key=openai_subscription_key,\n", + " api_version=openai_api_version)\n", + " set_default_openai_client(client)\n", + " set_default_openai_api(\"chat_completions\")\n", + " set_tracing_disabled(True)\n", + " \n", + " result = await Runner.run(triage_agent, input=\"Hola, ¿cómo estás?\")\n", + " print(\"💬\", result.final_output)\n", + "\n", + "if __name__ == \"__main__\":\n", + " asyncio.run(main())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "### 🧪 Run agent with Weather API from Azure API Management" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import asyncio, requests\n", + "from agents import Agent, Runner, function_tool\n", + "\n", + "@function_tool\n", + "def get_weather(city: str) -> str:\n", + " response = requests.get(f\"{apim_resource_gateway_url}/weatherservice/weather?city={city}\", headers = {'api-key':tools_subscription_key})\n", + " return response.text\n", + "\n", + "agent = Agent(\n", + " name=\"weather agent\",\n", + " instructions=\"You are a helpful assistant that provides wheather information. Always provide the temperature in Celsius.\",\n", + " tools=[get_weather],\n", + " model=openai_deployment_name\n", + ")\n", + "\n", + "async def main():\n", + " client = AsyncAzureOpenAI(azure_endpoint=apim_resource_gateway_url,\n", + " api_key=openai_subscription_key,\n", + " api_version=openai_api_version)\n", + " set_default_openai_client(client)\n", + " set_default_openai_api(\"chat_completions\")\n", + " set_tracing_disabled(True)\n", + "\n", + " result = await Runner.run(agent, input=\"Return a summary of the temperature in Seattle and 3 other sister cities in Europe?\")\n", + " print(\"💬\", result.final_output)\n", + "\n", + "if __name__ == \"__main__\":\n", + " asyncio.run(main())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "### 🧪 Run agent with OpenAPI Backend and Logic Apps workflow\n", + "\n", + "⚙️ **Tools**:\n", + "- Get Product Catalog - OpenAPI Backend mocked with an APIM policy.\n", + "- Place Order - A Logic Apps workflow that processes orders with a maximum of five items.\n", + "\n", + "✨ **Expected Behavior**:\n", + "- The agent receives a user request to order 11 smartphones.\n", + "- The agent calls the product catalog API to retrieve the product SKU and available stock quantity.\n", + "- If the order quantity exceeds available stock, the agent will respond that the order cannot be processed due to insufficient stock.\n", + "- If stock is available, the agent will initiate the order workflow, which will fail because the quantity exceeds the maximum limit of five items.\n", + "- As the agent was instructed to recover from errors, it will place multiple orders, each with a quantity below the maximum limit, ensuring the total equals the desired order quantity.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import asyncio, requests\n", + "from agents import Agent, Runner, function_tool\n", + "\n", + "@function_tool\n", + "def get_product_catalog(category: str) -> str:\n", + " response = requests.get(f\"{apim_resource_gateway_url}/catalogservice/product?category={category}\", headers = {'api-key':tools_subscription_key})\n", + " return response.text\n", + "\n", + "@function_tool\n", + "def place_order(sku: str, quantity: int) -> str:\n", + " response = requests.post(f\"{apim_resource_gateway_url}/orderservice/PlaceOrder/paths/invoke\", headers = {'api-key':tools_subscription_key}, json={\"sku\": sku, \"quantity\": quantity})\n", + " return response.text\n", + "\n", + "agent = Agent(\n", + " name=\"sales agent\",\n", + " instructions=\"You are a helpful sales assistant that helps users order products. Recover from errors if any and proceed with multiple orders if needed without user confirmation to fulfill the total order.\",\n", + " tools=[get_product_catalog, place_order],\n", + " model=openai_deployment_name\n", + ")\n", + "\n", + "async def main():\n", + " client = AsyncAzureOpenAI(azure_endpoint=apim_resource_gateway_url,\n", + " api_key=openai_subscription_key,\n", + " api_version=openai_api_version)\n", + " set_default_openai_client(client)\n", + " set_default_openai_api(\"chat_completions\")\n", + " set_tracing_disabled(True)\n", + "\n", + " result = await Runner.run(agent, input=\"Please order one smartphone for me and one for each of my ten friends.\")\n", + " print(\"💬\", result.final_output)\n", + "\n", + "if __name__ == \"__main__\":\n", + " asyncio.run(main())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "### 🔍 Analyze Application Insights custom metrics with a KQL query\n", + "\n", + "With this query you can get the custom metrics that were emitted by Azure APIM. Note that it may take a few minutes for data to become available." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import pandas as pd\n", + "\n", + "query = \"\\\"\" + \"customMetrics \\\n", + "| where name == 'Total Tokens' \\\n", + "| where timestamp >= ago(1h) \\\n", + "| extend parsedCustomDimensions = parse_json(customDimensions) \\\n", + "| extend apimSubscription = tostring(parsedCustomDimensions.['Subscription ID']) \\\n", + "| summarize TotalValue = sum(value) by apimSubscription, bin(timestamp, 1m) \\\n", + "| order by timestamp asc\" + \"\\\"\"\n", + "\n", + "output = utils.run(f\"az monitor app-insights query --app {app_insights_name} -g {resource_group_name} --analytics-query {query}\",\n", + " f\"App Insights query succeeded\", f\"App Insights query failed\")\n", + "\n", + "table = output.json_data['tables'][0]\n", + "df = pd.DataFrame(table.get(\"rows\"), columns = [col.get(\"name\") for col in table.get('columns')])\n", + "df['timestamp'] = pd.to_datetime(df['timestamp']).dt.strftime('%H:%M')\n", + "\n", + "df\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "### 🔍 Plot the custom metrics results" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import matplotlib.pyplot as plt\n", + "import matplotlib as mpl\n", + "mpl.rcParams['figure.figsize'] = [15, 7]\n", + "if df.empty:\n", + " print(\"No data to plot\")\n", + "else:\n", + " df_pivot = df.pivot(index='timestamp', columns='apimSubscription', values='TotalValue')\n", + " ax = df_pivot.plot(kind='bar', stacked=True)\n", + " plt.title('Total token usage over time by APIM Subscription')\n", + " plt.xlabel('Time')\n", + " plt.ylabel('Tokens')\n", + " plt.legend(title='APIM Subscription')\n", + " plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "### 🗑️ Clean up resources\n", + "\n", + "When you're finished with the lab, you should remove all your deployed resources from Azure to avoid extra charges and keep your Azure subscription uncluttered.\n", + "Use the [clean-up-resources notebook](clean-up-resources.ipynb) for that." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.10" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/labs/openai-agents-tf/output.tf b/labs/openai-agents-tf/output.tf new file mode 100644 index 0000000..2795bff --- /dev/null +++ b/labs/openai-agents-tf/output.tf @@ -0,0 +1,18 @@ +output "application_insights_instrumentation_key" { + value = azurerm_application_insights.appinsights.instrumentation_key + sensitive = true +} + +output "api_management_gateway_url" { + value = azapi_resource.apim.output.properties.gatewayUrl +} + +output "openai_subscription_key" { + value = azurerm_api_management_subscription.openai-subscription.primary_key + sensitive = true +} + +output "tools_subscription_key" { + value = azurerm_api_management_subscription.tools-apis-subscription.primary_key + sensitive = true +} diff --git a/labs/openai-agents-tf/place-order-openapi.json b/labs/openai-agents-tf/place-order-openapi.json new file mode 100644 index 0000000..68957af --- /dev/null +++ b/labs/openai-agents-tf/place-order-openapi.json @@ -0,0 +1,97 @@ +{ + "openapi": "3.0.1", + "info": { + "title": "Place Order API", + "description": "Place an Order to the specified sku and quantity.", + "version": "1.0" + }, + "servers": [ + { + "url": "https://replace-me.local/orderservice" + } + ], + "security": [ + { + "apiKeyHeader": [] + } + ], + "tags": [], + "paths": { + "/PlaceOrder/paths/invoke": { + "post": { + "summary": "PlaceOrder-invoke", + "description": "Place an Order to the specified sku and quantity.", + "operationId": "PlaceOrder-invoke", + "requestBody": { + "description": "The request body.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/request-PlaceOrder" + } + } + }, + "required": false + }, + "responses": { + "200": { + "description": "The Logic App Response.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PlaceOrderPathsInvokePost200ApplicationJsonResponse" + }, + "example": {} + } + } + }, + "500": { + "description": "The Logic App Response.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PlaceOrderPathsInvokePost500ApplicationJsonResponse" + }, + "example": {} + } + } + } + }, + "x-codegen-request-body-name": "request-PlaceOrder" + } + } + }, + "components": { + "schemas": { + "request-PlaceOrder": { + "type": "object", + "properties": { + "sku": { + "type": "string" + }, + "quantity": { + "type": "integer" + } + }, + "example": { + "sku": "string", + "quantity": 0 + } + }, + "PlaceOrderPathsInvokePost200ApplicationJsonResponse": { + "type": "object" + }, + "PlaceOrderPathsInvokePost500ApplicationJsonResponse": { + "type": "object" + } + }, + "securitySchemes": { + "apiKeyHeader": { + "type": "apiKey", + "name": "api-key", + "in": "header" + } + } + }, + "x-original-swagger-version": "2.0" + } \ No newline at end of file diff --git a/labs/openai-agents-tf/place-order-policy.xml b/labs/openai-agents-tf/place-order-policy.xml new file mode 100644 index 0000000..e590e26 --- /dev/null +++ b/labs/openai-agents-tf/place-order-policy.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/labs/openai-agents-tf/policy.xml b/labs/openai-agents-tf/policy.xml new file mode 100644 index 0000000..2379f0d --- /dev/null +++ b/labs/openai-agents-tf/policy.xml @@ -0,0 +1,25 @@ + + + + + + @("Bearer " + (string)context.Variables["managed-id-access-token"]) + + + + + + + + + + + + + + + + + + + diff --git a/labs/openai-agents-tf/product-catalog-mock-policy.xml b/labs/openai-agents-tf/product-catalog-mock-policy.xml new file mode 100644 index 0000000..cb5e35b --- /dev/null +++ b/labs/openai-agents-tf/product-catalog-mock-policy.xml @@ -0,0 +1,44 @@ + + + + + + @{ + var random = new Random(); + var names = new[] { "N/A" }; + var skus = new[] { "SKU-1234", "SKU-5678", "SKU-4321", "SKU-8765" }; + var storeLocations = new[] { "Lisbon", "Seattle", "London", "Madrid" }; + var category = context.Request.MatchedParameters["category"]; + switch (category.ToLower()) + { + case "electronics": + names = new[] { "Smartphone", "Tablet", "Laptop", "Smartwatch" }; + break; + case "appliances": + names = new[] { "Refrigerator", "Washing Machine", "Microwave", "Dishwasher" }; + break; + case "clothing": + names = new[] { "T-shirt", "Jeans", "Jacket", "Sneakers" }; + break; + } + + return new JObject( + new JProperty("name", names[random.Next(names.Length)]), + new JProperty("category", category), + new JProperty("sku", skus[random.Next(skus.Length)]), + new JProperty("stock", random.Next(1, 100)), + new JProperty("store_location", storeLocations[random.Next(storeLocations.Length)]) + ).ToString(); + } + + + + + + + + + + + + \ No newline at end of file diff --git a/labs/openai-agents-tf/product-catalog-openapi.json b/labs/openai-agents-tf/product-catalog-openapi.json new file mode 100644 index 0000000..4a070e5 --- /dev/null +++ b/labs/openai-agents-tf/product-catalog-openapi.json @@ -0,0 +1,113 @@ +{ + "openapi": "3.0.1", + "info": { + "title": "Product Catalog API", + "description": "API to retrieve the product catalog.", + "version": "1.0" + }, + "servers": [{ + "url": "https://replace-me.local/catalogservice" + }], + "paths": { + "/product": { + "get": { + "summary": "Get product details for a specified category", + "description": "Retrieves product details, for a specified category.", + "operationId": "get-product-details", + "parameters": [{ + "name": "category", + "in": "query", + "description": "Name of the category to retrieve product information for", + "required": true, + "schema": { + "type": "string", + "example": "Electronics" + } + }], + "responses": { + "200": { + "description": "Product information for the specified category", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProductInformation" + }, + "examples": { + "Smartphone": { + "summary": "Example response for Smartphone", + "value": { + "name": "Smartphone", + "category": "Electronics", + "sku": "ELEC1234", + "stock": 50, + "store_location": "Lisbon" + } + }, + "Laptop": { + "summary": "Example response for Laptop", + "value": { + "name": "Laptop", + "category": "Electronics", + "sku": "ELEC5678", + "stock": 30, + "store_location": "Seattle" + } + } + } + } + } + }, + "400": { + "description": "Bad request" + }, + "404": { + "description": "Category not found" + }, + "500": { + "description": "Internal server error" + } + } + } + } + }, + "components": { + "schemas": { + "ProductInformation": { + "type": "object", + "properties": { + "name": { + "type": "string", + "example": "Smartphone" + }, + "category": { + "type": "string", + "example": "Electronics" + }, + "sku": { + "type": "string", + "example": "ELEC1234" + }, + "stock": { + "type": "number", + "format": "int32", + "example": 60 + }, + "store_location": { + "type": "string", + "example": "Lisbon" + } + } + } + }, + "securitySchemes": { + "apiKeyHeader": { + "type": "apiKey", + "name": "api-key", + "in": "header" + } + } + }, + "security": [{ + "apiKeyHeader": [] + }] +} \ No newline at end of file diff --git a/labs/openai-agents-tf/providers.tf b/labs/openai-agents-tf/providers.tf new file mode 100644 index 0000000..b4a481a --- /dev/null +++ b/labs/openai-agents-tf/providers.tf @@ -0,0 +1,29 @@ +terraform { + + required_version = ">=1.8" + + required_providers { + azurerm = { + source = "hashicorp/azurerm" + version = ">= 4.16.0" + } + azapi = { + source = "Azure/azapi" + version = ">= 2.2.0" + } + random = { + source = "hashicorp/random" + version = ">= 3.6.3" + } + } +} + +provider "azurerm" { + features{} +} + +provider "azapi" { +} + +provider "random" { +} diff --git a/labs/openai-agents-tf/terraform.tfvars b/labs/openai-agents-tf/terraform.tfvars new file mode 100644 index 0000000..871c629 --- /dev/null +++ b/labs/openai-agents-tf/terraform.tfvars @@ -0,0 +1,16 @@ + +resource_group_name = "lab-openai-agents-tf" +resource_group_location = "uksouth" +apim_sku = "Basicv2" +openai_deployment_name = "gpt-4o-mini" +openai_model_name = "gpt-4o-mini" +openai_model_version = "2024-07-18" +openai_model_capacity = "8" +openai_api_version = "2024-10-21" +openai_config = { + openai-1 = { + name = "openai1", + location = "uksouth", + } +} + diff --git a/labs/openai-agents-tf/variables.tf b/labs/openai-agents-tf/variables.tf new file mode 100644 index 0000000..b37f84a --- /dev/null +++ b/labs/openai-agents-tf/variables.tf @@ -0,0 +1,92 @@ +variable "resource_group_name" { + type = string + description = "The name of the resource group." +} + +variable "resource_group_location" { + type = string + description = "The location of the resource group." + default = "eastus" +} + +variable "apim_resource_name" { + type = string + description = "The name of the API Management resource." + default = "apim" +} + +variable "apim_sku" { + type = string + description = "The SKU of the API Management resource." + default = "Developer" +} + +variable "openai_config" { + description = "Configuration for OpenAI accounts" + type = map(object({ + location = string + name = string + })) +} + +variable "openai_api_version" { + type = string + description = "The API version for OpenAI Cognitive Service." + default = "2024-10-21" + +} + +variable "openai_sku" { + type = string + description = "The SKU for OpenAI Cognitive Service." + default = "S0" +} + +variable "openai_deployment_name" { + type = string + description = "The name of the OpenAI deployment." +} + +variable "openai_model_name" { + type = string + description = "The name of the OpenAI model." +} + +variable "openai_model_version" { + type = string + description = "The version of the OpenAI model." +} + +variable "openai_model_capacity" { + type = number + description = "The capacity of the OpenAI model." + default = 1 +} + +variable "openai_api_spec_url" { + type = string + default = "https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cognitiveservices/data-plane/AzureOpenAI/inference/stable/2024-10-21/inference.json" +} +variable "weather_api_path" { + type = string + description = "The path for the Weather API." + default = "weatherservice" +} + +variable "place_order_api_path" { + type = string + description = "The path for the Place Order API." + default = "orderservice" +} + +variable "product_catalog_api_path" { + type = string + description = "The path for the Product Catalog API." + default = "catalogservice" +} + +variable "location" { + type = string + description = "The location for the resources." + default = "eastus" +}