From 140a57a6056732193068188897e8689eb5443d8d Mon Sep 17 00:00:00 2001 From: Richard Park <51494936+richardpark-msft@users.noreply.github.com> Date: Wed, 26 Jul 2023 11:08:02 -0700 Subject: [PATCH] [azopenai] Move azopenai from `cognitiveservices/azopenai` to `ai/azopenai` (#21264) Part of the fix for #21260. - Moved the code (to preserve history) into `ai/azopenai` - Marked `cognitiveservices/azopenai`, prepping to release one last release to deprecate it. We still need to submit an issue to de-list `cognitiveservices/azopenai` but I believe this takes care of our end. --- sdk/ai/azopenai/CHANGELOG.md | 15 + sdk/ai/azopenai/LICENSE.txt | 21 + sdk/ai/azopenai/README.md | 98 ++ sdk/ai/azopenai/assets.json | 6 + sdk/ai/azopenai/autorest.md | 297 ++++ sdk/ai/azopenai/build.go | 14 + sdk/ai/azopenai/ci.yml | 28 + sdk/ai/azopenai/client.go | 231 +++ .../azopenai/client_chat_completions_test.go | 233 +++ sdk/ai/azopenai/client_completions_test.go | 74 + sdk/ai/azopenai/client_embeddings_test.go | 99 ++ sdk/ai/azopenai/client_functions_test.go | 94 + sdk/ai/azopenai/client_rai_test.go | 93 + sdk/ai/azopenai/client_shared_test.go | 260 +++ sdk/ai/azopenai/client_test.go | 42 + sdk/ai/azopenai/constants.go | 131 ++ sdk/ai/azopenai/custom_client.go | 259 +++ sdk/ai/azopenai/custom_client_functions.go | 41 + sdk/ai/azopenai/custom_client_image.go | 88 + sdk/ai/azopenai/custom_client_image_test.go | 118 ++ sdk/ai/azopenai/custom_client_test.go | 175 ++ sdk/ai/azopenai/custom_models.go | 97 ++ sdk/ai/azopenai/custom_models_test.go | 66 + sdk/ai/azopenai/event_reader.go | 63 + sdk/ai/azopenai/event_reader_test.go | 42 + .../example_client_createimage_test.go | 66 + .../example_client_embeddings_test.go | 55 + .../example_client_getchatcompletions_test.go | 270 +++ .../example_client_getcompletions_test.go | 116 ++ sdk/ai/azopenai/example_client_test.go | 63 + sdk/ai/azopenai/genopenapi3.ps1 | 5 + sdk/ai/azopenai/go.mod | 28 + sdk/ai/azopenai/go.sum | 46 + sdk/ai/azopenai/main_test.go | 14 + sdk/ai/azopenai/models.go | 707 ++++++++ sdk/ai/azopenai/models_serde.go | 1551 +++++++++++++++++ sdk/ai/azopenai/options.go | 31 + sdk/ai/azopenai/policy_apikey.go | 46 + sdk/ai/azopenai/policy_apikey_test.go | 84 + sdk/ai/azopenai/response_types.go | 39 + sdk/ai/azopenai/sample.env | 19 + sdk/ai/azopenai/testdata/.gitignore | 3 + .../content_filter_response_error.json | 30 + sdk/ai/azopenai/testdata/package-lock.json | 1009 +++++++++++ sdk/ai/azopenai/testdata/package.json | 16 + sdk/ai/azopenai/testdata/tsp-location.yaml | 4 + sdk/ai/azopenai/testdata/tspconfig.yaml | 11 + sdk/ai/azopenai/version.go | 11 + sdk/cognitiveservices/azopenai/CHANGELOG.md | 8 +- sdk/cognitiveservices/azopenai/README.md | 99 +- sdk/cognitiveservices/azopenai/go.mod | 1 + 51 files changed, 6913 insertions(+), 104 deletions(-) create mode 100644 sdk/ai/azopenai/CHANGELOG.md create mode 100644 sdk/ai/azopenai/LICENSE.txt create mode 100644 sdk/ai/azopenai/README.md create mode 100644 sdk/ai/azopenai/assets.json create mode 100644 sdk/ai/azopenai/autorest.md create mode 100644 sdk/ai/azopenai/build.go create mode 100644 sdk/ai/azopenai/ci.yml create mode 100644 sdk/ai/azopenai/client.go create mode 100644 sdk/ai/azopenai/client_chat_completions_test.go create mode 100644 sdk/ai/azopenai/client_completions_test.go create mode 100644 sdk/ai/azopenai/client_embeddings_test.go create mode 100644 sdk/ai/azopenai/client_functions_test.go create mode 100644 sdk/ai/azopenai/client_rai_test.go create mode 100644 sdk/ai/azopenai/client_shared_test.go create mode 100644 sdk/ai/azopenai/client_test.go create mode 100644 sdk/ai/azopenai/constants.go create mode 100644 sdk/ai/azopenai/custom_client.go create mode 100644 sdk/ai/azopenai/custom_client_functions.go create mode 100644 sdk/ai/azopenai/custom_client_image.go create mode 100644 sdk/ai/azopenai/custom_client_image_test.go create mode 100644 sdk/ai/azopenai/custom_client_test.go create mode 100644 sdk/ai/azopenai/custom_models.go create mode 100644 sdk/ai/azopenai/custom_models_test.go create mode 100644 sdk/ai/azopenai/event_reader.go create mode 100644 sdk/ai/azopenai/event_reader_test.go create mode 100644 sdk/ai/azopenai/example_client_createimage_test.go create mode 100644 sdk/ai/azopenai/example_client_embeddings_test.go create mode 100644 sdk/ai/azopenai/example_client_getchatcompletions_test.go create mode 100644 sdk/ai/azopenai/example_client_getcompletions_test.go create mode 100644 sdk/ai/azopenai/example_client_test.go create mode 100644 sdk/ai/azopenai/genopenapi3.ps1 create mode 100644 sdk/ai/azopenai/go.mod create mode 100644 sdk/ai/azopenai/go.sum create mode 100644 sdk/ai/azopenai/main_test.go create mode 100644 sdk/ai/azopenai/models.go create mode 100644 sdk/ai/azopenai/models_serde.go create mode 100644 sdk/ai/azopenai/options.go create mode 100644 sdk/ai/azopenai/policy_apikey.go create mode 100644 sdk/ai/azopenai/policy_apikey_test.go create mode 100644 sdk/ai/azopenai/response_types.go create mode 100644 sdk/ai/azopenai/sample.env create mode 100644 sdk/ai/azopenai/testdata/.gitignore create mode 100644 sdk/ai/azopenai/testdata/content_filter_response_error.json create mode 100644 sdk/ai/azopenai/testdata/package-lock.json create mode 100644 sdk/ai/azopenai/testdata/package.json create mode 100644 sdk/ai/azopenai/testdata/tsp-location.yaml create mode 100644 sdk/ai/azopenai/testdata/tspconfig.yaml create mode 100644 sdk/ai/azopenai/version.go diff --git a/sdk/ai/azopenai/CHANGELOG.md b/sdk/ai/azopenai/CHANGELOG.md new file mode 100644 index 000000000000..17d13f93f73c --- /dev/null +++ b/sdk/ai/azopenai/CHANGELOG.md @@ -0,0 +1,15 @@ +# Release History + +## 0.1.1 (Unreleased) + +### Features Added + +### Breaking Changes + +### Bugs Fixed + +### Other Changes + +## 0.1.0 (2023-07-20) + +* Initial release of the `azopenai` library diff --git a/sdk/ai/azopenai/LICENSE.txt b/sdk/ai/azopenai/LICENSE.txt new file mode 100644 index 000000000000..ec703274aadd --- /dev/null +++ b/sdk/ai/azopenai/LICENSE.txt @@ -0,0 +1,21 @@ + MIT License + +Copyright (c) Microsoft Corporation. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE \ No newline at end of file diff --git a/sdk/ai/azopenai/README.md b/sdk/ai/azopenai/README.md new file mode 100644 index 000000000000..c564dc79bb54 --- /dev/null +++ b/sdk/ai/azopenai/README.md @@ -0,0 +1,98 @@ +# Azure OpenAI client module for Go + +NOTE: this client can be used with Azure OpenAI and OpenAI. + +Azure OpenAI Service provides access to OpenAI's powerful language models including the GPT-4, GPT-35-Turbo, and Embeddings model series, as well as image generation using DALL-E. + +[Source code][azopenai_repo] | [Package (pkg.go.dev)][azopenai_pkg_go] | [REST API documentation][openai_rest_docs] | [Product documentation][openai_docs] + +## Getting started + +### Prerequisites + +* Go, version 1.18 or higher - [Install Go](https://go.dev/doc/install) +* [Azure subscription][azure_sub] +* [Azure OpenAI access][azure_openai_access] + +### Install the packages + +Install the `azopenai` and `azidentity` modules with `go get`: + +```bash +go get github.com/Azure/azure-sdk-for-go/sdk/ai/azopenai + +# optional +go get github.com/Azure/azure-sdk-for-go/sdk/azidentity +``` + +The [azidentity][azure_identity] module is used for Azure Active Directory authentication with Azure OpenAI. + +### Authentication + +#### Azure OpenAI + +Azure OpenAI clients can authenticate using Azure Active Directory or with an API key: + +* Using Azure Active Directory, with a TokenCredential: [example](https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/sdk/ai/azopenai#example-NewClient) +* Using an API key: [example](https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/sdk/ai/azopenai#example-NewClientWithKeyCredential) + +#### OpenAI + +OpenAI supports connecting using an API key: [example](https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/sdk/ai/azopenai#example-NewClientForOpenAI) + +## Key concepts + +See [Key concepts][openai_key_concepts] in the product documentation for more details about general concepts. + +# Examples + +Examples for various scenarios can be found on [pkg.go.dev](https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/sdk/ai/azopenai#pkg-examples) or in the example*_test.go files in our GitHub repo for [azopenai](https://github.com/Azure/azure-sdk-for-go/blob/main/sdk/cognitiveservices/azopenai). + +## Troubleshooting + +### Error Handling + +All methods that send HTTP requests return `*azcore.ResponseError` when these requests fail. `ResponseError` has error details and the raw response from the service. + +### Logging + +This module uses the logging implementation in `azcore`. To turn on logging for all Azure SDK modules, set `AZURE_SDK_GO_LOGGING` to `all`. By default, the logger writes to stderr. Use the `azcore/log` package to control log output. For example, logging only HTTP request and response events, and printing them to stdout: + +```go +import azlog "github.com/Azure/azure-sdk-for-go/sdk/azcore/log" + +// Print log events to stdout +azlog.SetListener(func(cls azlog.Event, msg string) { + fmt.Println(msg) +}) + +// Includes only requests and responses in credential logs +azlog.SetEvents(azlog.EventRequest, azlog.EventResponse) +``` + +## Contributing + +This project welcomes contributions and suggestions. Most contributions require you to agree to a [Contributor License Agreement (CLA)][cla] declaring that you have the right to, and actually do, grant us the rights to use your contribution. + +When you submit a pull request, a CLA-bot will automatically determine whether you need to provide a CLA and decorate +the PR appropriately (e.g., label, comment). Simply follow the instructions provided by the bot. You will only need to +do this once across all repos using our CLA. + +This project has adopted the [Microsoft Open Source Code of Conduct][coc]. For more information, see +the [Code of Conduct FAQ][coc_faq] or contact [opencode@microsoft.com][coc_contact] with any additional questions or +comments. + + +[azure_openai_access]: https://learn.microsoft.com/azure/cognitive-services/openai/overview#how-do-i-get-access-to-azure-openai +[azopenai_repo]: https://github.com/Azure/azure-sdk-for-go/tree/main/sdk/cognitiveservices/azopenai +[azopenai_pkg_go]: https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/sdk/ai/azopenai +[azure_identity]: https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/sdk/azidentity +[azure_sub]: https://azure.microsoft.com/free/ +[openai_docs]: https://learn.microsoft.com/azure/cognitive-services/openai +[openai_key_concepts]: https://learn.microsoft.com/azure/cognitive-services/openai/overview#key-concepts +[openai_rest_docs]: https://learn.microsoft.com/azure/cognitive-services/openai/reference +[cla]: https://cla.microsoft.com +[coc]: https://opensource.microsoft.com/codeofconduct/ +[coc_faq]: https://opensource.microsoft.com/codeofconduct/faq/ +[coc_contact]: mailto:opencode@microsoft.com +[azure_openai_quickstart]: https://learn.microsoft.com/azure/cognitive-services/openai/quickstart \ No newline at end of file diff --git a/sdk/ai/azopenai/assets.json b/sdk/ai/azopenai/assets.json new file mode 100644 index 000000000000..80e73970708d --- /dev/null +++ b/sdk/ai/azopenai/assets.json @@ -0,0 +1,6 @@ +{ + "AssetsRepo": "Azure/azure-sdk-assets", + "AssetsRepoPrefixPath": "go", + "TagPrefix": "go/cognitiveservices/azopenai", + "Tag": "go/cognitiveservices/azopenai_8fdad86997" +} diff --git a/sdk/ai/azopenai/autorest.md b/sdk/ai/azopenai/autorest.md new file mode 100644 index 000000000000..8d0eb387818f --- /dev/null +++ b/sdk/ai/azopenai/autorest.md @@ -0,0 +1,297 @@ +# Go + +These settings apply only when `--go` is specified on the command line. + +``` yaml +input-file: +#- https://raw.githubusercontent.com/Azure/azure-rest-api-specs/13a645b66b741e3cc2ef378cb81974b30e6a7a86/specification/cognitiveservices/AzureOpenAI/inference/2023-06-01-preview/generated.json +- ./testdata/generated/openapi3.json + +output-folder: ../azopenai +clear-output-folder: false +module: github.com/Azure/azure-sdk-for-go/sdk/ai/azopenai +license-header: MICROSOFT_MIT_NO_VERSION +openapi-type: data-plane +go: true +use: "@autorest/go@4.0.0-preview.52" +title: "OpenAI" +slice-elements-byval: true +# can't use this since it removes an innererror type that we want () +# remove-non-reference-schema: true +``` + +## Transformations + +``` yaml +directive: + # Add x-ms-parameter-location to parameters in x-ms-parameterized-host + - from: openapi-document + where: $.servers.0.variables.endpoint + debug: true + transform: $["x-ms-parameter-location"] = "client"; + + # Make deploymentId a client parameter + # This must be done in each operation as the parameter is not defined in the components section + - from: openapi-document + where: $.paths..parameters..[?(@.name=='deploymentId')] + transform: $["x-ms-parameter-location"] = "client"; + + - from: openapi-document + where: $..paths["/deployments/{deploymentId}/completions"].post.requestBody + transform: $["required"] = true; + - from: openapi-document + where: $.paths["/deployments/{deploymentId}/embeddings"].post.requestBody + transform: $["required"] = true; + + # get rid of these auto-generated LRO status methods that aren't exposed. + - from: openapi-document + where: $.paths + transform: delete $["/operations/images/{operationId}"] + + # Remove stream property from CompletionsOptions and ChatCompletionsOptions + - from: openapi-document + where: $.components.schemas["CompletionsOptions"] + transform: delete $.properties.stream; + - from: openapi-document + where: $.components.schemas["ChatCompletionsOptions"] + transform: delete $.properties.stream; + + # Replace anyOf schemas with an empty schema (no type) to get an "any" type generated + - from: openapi-document + where: '$.components.schemas["EmbeddingsOptions"].properties["input"]' + transform: delete $.anyOf; + + - from: openapi-document + where: $.paths["/images/generations:submit"].post + transform: $["x-ms-long-running-operation"] = true; + + # Fix autorest bug + - from: openapi-document + where: $.components.schemas["BatchImageGenerationOperationResponse"].properties + transform: | + $.result["$ref"] = "#/components/schemas/ImageGenerations"; delete $.allOf; + $.status["$ref"] = "#/components/schemas/AzureOpenAIOperationState"; delete $.allOf; + $.error["$ref"] = "#/components/schemas/Azure.Core.Foundations.Error"; delete $.allOf; + - from: openapi-document + where: $.components.schemas["ChatMessage"].properties.role + transform: $["$ref"] = "#/components/schemas/ChatRole"; delete $.oneOf; + - from: openapi-document + where: $.components.schemas["Choice"].properties.finish_reason + transform: $["$ref"] = "#/components/schemas/CompletionsFinishReason"; delete $.oneOf; + - from: openapi-document + where: $.components.schemas["ImageOperation"].properties.status + transform: $["$ref"] = $.anyOf[0]["$ref"];delete $.anyOf; + - from: openapi-document + where: $.components.schemas.ImageGenerationOptions.properties + transform: | + $.size["$ref"] = "#/components/schemas/ImageSize"; delete $.allOf; + $.response_format["$ref"] = "#/components/schemas/ImageGenerationResponseFormat"; delete $.allOf; + - from: openapi-document + where: $.components.schemas["ImageOperationResponse"].properties + transform: | + $.status["$ref"] = "#/components/schemas/State"; delete $.status.allOf; + $.result["$ref"] = "#/components/schemas/ImageResponse"; delete $.status.allOf; + - from: openapi-document + where: $.components.schemas["ImageOperationStatus"].properties.status + transform: $["$ref"] = "#/components/schemas/State"; delete $.allOf; + - from: openapi-document + where: $.components.schemas["ContentFilterResult"].properties.severity + transform: $["$ref"] = "#/components/schemas/ContentFilterSeverity"; delete $.allOf; + - from: openapi-document + where: $.components.schemas["ChatChoice"].properties.finish_reason + transform: $["$ref"] = "#/components/schemas/CompletionsFinishReason"; delete $.oneOf; + # Fix "AutoGenerated" models + - from: openapi-document + where: $.components.schemas["ChatCompletions"].properties.usage + transform: > + delete $.allOf; + $["$ref"] = "#/components/schemas/CompletionsUsage"; + - from: openapi-document + where: $.components.schemas["Completions"].properties.usage + transform: > + delete $.allOf; + $["$ref"] = "#/components/schemas/CompletionsUsage"; + + # + # strip out the deploymentID validation code - we absorbed this into the endpoint. + # + # urlPath := "/deployments/{deploymentId}/embeddings" + # if client.deploymentID == "" { + # return nil, errors.New("parameter client.deploymentID cannot be empty") + # } + # urlPath = strings.ReplaceAll(urlPath, "{deploymentId}", url.PathEscape(client.deploymentID)) + - from: client.go + where: $ + transform: >- + return $.replace( + /(\s+)urlPath\s*:=\s*"\/deployments\/\{deploymentId\}\/([^"]+)".+?url\.PathEscape.+?\n/gs, + "$1urlPath := \"$2\"\n") + + # Unexport the the poller state enum. + - from: + - constants.go + - models.go + where: $ + transform: return $.replace(/AzureOpenAIOperationState/g, "azureOpenAIOperationState"); + + # splice out the auto-generated `deploymentID` field from the client + - from: client.go + where: $ + transform: >- + return $.replace( + /(type Client struct[^}]+})/s, + "type Client struct {\ninternal *azcore.Client; clientData;\n}") + + - from: + - models_serde.go + - models.go + where: $ + transform: return $.replace(/AzureCoreFoundations/g, "azureCoreFoundations"); + - from: + - models_serde.go + - models.go + where: $ + transform: return $.replace(/(?:\/\/.*\s)?func \(\w \*?(?:ErrorResponse|ErrorResponseError|InnerError|InnerErrorInnererror)\).*\{\s(?:.+\s)+\}\s/g, ""); + + - from: constants.go + where: $ + transform: >- + return $.replace( + /type ServiceAPIVersions string.+PossibleServiceAPIVersionsValues.+?\n}/gs, + "") + + # delete client name prefix from method options and response types + - from: + - client.go + - models.go + - options.go + - response_types.go + where: $ + transform: return $.replace(/Client(\w+)((?:Options|Response))/g, "$1$2"); + + # allow interception of formatting the URL path + - from: client.go + where: $ + transform: | + return $ + .replace(/runtime\.JoinPaths\(client.endpoint, urlPath\)/g, "client.formatURL(urlPath, getDeploymentID(body))"); + + # Some ImageGenerations hackery to represent the ImageLocation/ImagePayload polymorphism. + # - Remove the auto-generated ImageGenerationsDataItem. + # - Replace the ImageGenerations.Data type with []ImageGenerationDataItem + # - from: models.go + # where: $ + # transform: | + # return $.replace(/type ImageGenerationsDataItem struct {[^}]+}/, "// ImageGenerationsDataItem represents an image URL or payload\ntype ImageGenerationsDataItem struct{\nImageLocation\nImagePayload\n}") + # $.replace(/(type ImageGenerations struct.+?)Data any/g, "$1Data []ImageGenerationsDataItem") + + - from: models.go + where: $ + transform: | + return $.replace(/(type ImageGenerations struct.+?)Data any/sg, "$1Data []ImageGenerationsDataItem") + + # delete the auto-generated ImageGenerationsDataItem, we handle that custom + - from: models.go + where: $ + transform: return $.replace(/\/\/ ImageGenerationsDataItem represents[^}]+}/s, ""); + + # rename the image constants + - from: constants.go + where: $ + transform: | + return $.replace(/ImageSizeFiveHundredTwelveX512/g, "ImageSize512x512") + .replace(/ImageSizeOneThousandTwentyFourX1024/g, "ImageSize1024x1024") + .replace(/ImageSizeTwoHundredFiftySixX256/g, "ImageSize256x256"); + + # scrub the Image(Payload|Location) deserializers. + - from: models_serde.go + where: $ + transform: | + return $.replace(/\/\/ UnmarshalJSON implements the json.Unmarshaller interface for type ImagePayload.+?\n}/s, "") + .replace(/\/\/ MarshalJSON implements the json.Marshaller interface for type ImagePayload.+?\n}/s, "") + .replace(/\/\/ UnmarshalJSON implements the json.Unmarshaller interface for type ImageLocation.+?\n}/s, "") + .replace(/\/\/ MarshalJSON implements the json.Marshaller interface for type ImageLocation.+?\n}/s, ""); + + # hide the image generation pollers. + - rename-operation: + from: beginAzureBatchImageGeneration + to: azureBatchImageGenerationInternal + - from: + - client.go + - models.go + - models_serde.go + - options.go + - response_types.go + where: $ + transform: | + return $.replace(/GetAzureBatchImageGenerationOperationStatusResponse/g, "getAzureBatchImageGenerationOperationStatusResponse") + .replace(/AzureBatchImageGenerationInternalResponse/g, "azureBatchImageGenerationInternalResponse") + .replace(/GetAzureBatchImageGenerationOperationStatusOptions/g, "getAzureBatchImageGenerationOperationStatusOptions") + .replace(/GetAzureBatchImageGenerationOperationStatus/g, "getAzureBatchImageGenerationOperationStatus") + .replace(/BeginAzureBatchImageGenerationInternal/g, "beginAzureBatchImageGeneration") + .replace(/BatchImageGenerationOperationResponse/g, "batchImageGenerationOperationResponse"); + + # BUG: ChatCompletionsOptionsFunctionCall is another one of those "here's mutually exclusive values" options... + - from: + - models.go + - models_serde.go + where: $ + transform: | + return $ + .replace(/populateAny\(objectMap, "function_call", c.FunctionCall\)/, 'populate(objectMap, "function_call", c.FunctionCall)') + .replace(/\/\/ ChatCompletionsOptionsFunctionCall.+?\n}/, "") + .replace(/FunctionCall any/, "FunctionCall *ChatCompletionsOptionsFunctionCall"); + + # fix some casing + - from: + - client.go + - models.go + - models_serde.go + - options.go + - response_types.go + where: $ + transform: return $.replace(/Logprobs/g, "LogProbs") + + # delete ContentFilterResult in favor of our custom representation. + - from: + - models.go + - models_serde.go + where: $ + transform: | + return $.replace(/\/\/ ContentFilterResult.+?\n}/s, "") + .replace(/\/\/ MarshalJSON implements the json.Marshaller interface for type ContentFilterResult.+?\n}/s, "") + .replace(/\/\/ UnmarshalJSON implements the json.Unmarshaller interface for type ContentFilterResult.+?\n}/s, ""); + + - from: constants.go + where: $ + transform: return $.replace(/\/\/ PossibleazureOpenAIOperationStateValues returns.+?\n}/s, ""); + + # fix incorrect property name for content filtering + # TODO: I imagine we should able to fix this in the tsp? + - from: models_serde.go + where: $ + transform: | + return $ + .replace(/ case "selfHarm":/g, ' case "self_harm":') + .replace(/populate\(objectMap, "selfHarm", c.SelfHarm\)/g, 'populate(objectMap, "self_harm", c.SelfHarm)'); + + - from: client.go + where: $ + transform: return $.replace(/runtime\.NewResponseError/sg, "client.newError"); + + # + # rename `Model` to `DeploymentID` + # + - from: models.go + where: $ + transform: | + return $ + .replace(/\/\/ The model name.*?Model \*string/sg, "// REQUIRED: DeploymentID specifies the name of the deployment (for Azure OpenAI) or model (for OpenAI) to use for this request.\nDeploymentID string"); + + - from: models_serde.go + where: $ + transform: | + return $ + .replace(/populate\(objectMap, "model", (c|e).Model\)/g, 'populate(objectMap, "model", &$1.DeploymentID)') + .replace(/err = unpopulate\(val, "Model", &(c|e).Model\)/g, 'err = unpopulate(val, "Model", &$1.DeploymentID)'); +``` diff --git a/sdk/ai/azopenai/build.go b/sdk/ai/azopenai/build.go new file mode 100644 index 000000000000..88088a8e55c1 --- /dev/null +++ b/sdk/ai/azopenai/build.go @@ -0,0 +1,14 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +//go:build go1.18 +// +build go1.18 + +//go:generate pwsh ./genopenapi3.ps1 +//go:generate autorest ./autorest.md +//go:generate go mod tidy +//go:generate goimports -w . + +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +package azopenai diff --git a/sdk/ai/azopenai/ci.yml b/sdk/ai/azopenai/ci.yml new file mode 100644 index 000000000000..143bc1e6c2fd --- /dev/null +++ b/sdk/ai/azopenai/ci.yml @@ -0,0 +1,28 @@ +# NOTE: Please refer to https://aka.ms/azsdk/engsys/ci-yaml before editing this file. +trigger: + branches: + include: + - main + - feature/* + - hotfix/* + - release/* + paths: + include: + - sdk/ai/azopenai + - eng/ + +pr: + branches: + include: + - main + - feature/* + - hotfix/* + - release/* + paths: + include: + - sdk/ai/azopenai + +stages: + - template: /eng/pipelines/templates/jobs/archetype-sdk-client.yml + parameters: + ServiceDirectory: "ai/azopenai" diff --git a/sdk/ai/azopenai/client.go b/sdk/ai/azopenai/client.go new file mode 100644 index 000000000000..ad5759b38033 --- /dev/null +++ b/sdk/ai/azopenai/client.go @@ -0,0 +1,231 @@ +//go:build go1.18 +// +build go1.18 + +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// Code generated by Microsoft (R) AutoRest Code Generator. DO NOT EDIT. +// Changes may cause incorrect behavior and will be lost if the code is regenerated. + +package azopenai + +import ( + "context" + "net/http" + + "github.com/Azure/azure-sdk-for-go/sdk/azcore" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime" +) + +// Client contains the methods for the OpenAI group. +// Don't use this type directly, use a constructor function instead. +type Client struct { + internal *azcore.Client + clientData +} + +// beginAzureBatchImageGeneration - Starts the generation of a batch of images from a text caption +// If the operation fails it returns an *azcore.ResponseError type. +// +// Generated from API version 2023-07-01-preview +// - options - beginAzureBatchImageGenerationOptions contains the optional parameters for the Client.beginAzureBatchImageGeneration +// method. +func (client *Client) beginAzureBatchImageGeneration(ctx context.Context, body ImageGenerationOptions, options *beginAzureBatchImageGenerationOptions) (*runtime.Poller[azureBatchImageGenerationInternalResponse], error) { + if options == nil || options.ResumeToken == "" { + resp, err := client.azureBatchImageGenerationInternal(ctx, body, options) + if err != nil { + return nil, err + } + poller, err := runtime.NewPoller[azureBatchImageGenerationInternalResponse](resp, client.internal.Pipeline(), nil) + return poller, err + } else { + return runtime.NewPollerFromResumeToken[azureBatchImageGenerationInternalResponse](options.ResumeToken, client.internal.Pipeline(), nil) + } +} + +// AzureBatchImageGenerationInternal - Starts the generation of a batch of images from a text caption +// If the operation fails it returns an *azcore.ResponseError type. +// +// Generated from API version 2023-07-01-preview +func (client *Client) azureBatchImageGenerationInternal(ctx context.Context, body ImageGenerationOptions, options *beginAzureBatchImageGenerationOptions) (*http.Response, error) { + var err error + req, err := client.azureBatchImageGenerationInternalCreateRequest(ctx, body, options) + if err != nil { + return nil, err + } + httpResp, err := client.internal.Pipeline().Do(req) + if err != nil { + return nil, err + } + if !runtime.HasStatusCode(httpResp, http.StatusAccepted) { + err = client.newError(httpResp) + return nil, err + } + return httpResp, nil +} + +// azureBatchImageGenerationInternalCreateRequest creates the AzureBatchImageGenerationInternal request. +func (client *Client) azureBatchImageGenerationInternalCreateRequest(ctx context.Context, body ImageGenerationOptions, options *beginAzureBatchImageGenerationOptions) (*policy.Request, error) { + urlPath := "/images/generations:submit" + req, err := runtime.NewRequest(ctx, http.MethodPost, client.formatURL(urlPath, getDeploymentID(body))) + if err != nil { + return nil, err + } + reqQP := req.Raw().URL.Query() + reqQP.Set("api-version", "2023-07-01-preview") + req.Raw().URL.RawQuery = reqQP.Encode() + req.Raw().Header["Accept"] = []string{"application/json"} + if err := runtime.MarshalAsJSON(req, body); err != nil { + return nil, err + } + return req, nil +} + +// GetChatCompletions - Gets chat completions for the provided chat messages. Completions support a wide variety of tasks +// and generate text that continues from or "completes" provided prompt data. +// If the operation fails it returns an *azcore.ResponseError type. +// +// Generated from API version 2023-07-01-preview +// - options - GetChatCompletionsOptions contains the optional parameters for the Client.GetChatCompletions method. +func (client *Client) GetChatCompletions(ctx context.Context, body ChatCompletionsOptions, options *GetChatCompletionsOptions) (GetChatCompletionsResponse, error) { + var err error + req, err := client.getChatCompletionsCreateRequest(ctx, body, options) + if err != nil { + return GetChatCompletionsResponse{}, err + } + httpResp, err := client.internal.Pipeline().Do(req) + if err != nil { + return GetChatCompletionsResponse{}, err + } + if !runtime.HasStatusCode(httpResp, http.StatusOK) { + err = client.newError(httpResp) + return GetChatCompletionsResponse{}, err + } + resp, err := client.getChatCompletionsHandleResponse(httpResp) + return resp, err +} + +// getChatCompletionsCreateRequest creates the GetChatCompletions request. +func (client *Client) getChatCompletionsCreateRequest(ctx context.Context, body ChatCompletionsOptions, options *GetChatCompletionsOptions) (*policy.Request, error) { + urlPath := "chat/completions" + req, err := runtime.NewRequest(ctx, http.MethodPost, client.formatURL(urlPath, getDeploymentID(body))) + if err != nil { + return nil, err + } + reqQP := req.Raw().URL.Query() + reqQP.Set("api-version", "2023-07-01-preview") + req.Raw().URL.RawQuery = reqQP.Encode() + req.Raw().Header["Accept"] = []string{"application/json"} + if err := runtime.MarshalAsJSON(req, body); err != nil { + return nil, err + } + return req, nil +} + +// getChatCompletionsHandleResponse handles the GetChatCompletions response. +func (client *Client) getChatCompletionsHandleResponse(resp *http.Response) (GetChatCompletionsResponse, error) { + result := GetChatCompletionsResponse{} + if err := runtime.UnmarshalAsJSON(resp, &result.ChatCompletions); err != nil { + return GetChatCompletionsResponse{}, err + } + return result, nil +} + +// GetCompletions - Gets completions for the provided input prompts. Completions support a wide variety of tasks and generate +// text that continues from or "completes" provided prompt data. +// If the operation fails it returns an *azcore.ResponseError type. +// +// Generated from API version 2023-07-01-preview +// - options - GetCompletionsOptions contains the optional parameters for the Client.GetCompletions method. +func (client *Client) GetCompletions(ctx context.Context, body CompletionsOptions, options *GetCompletionsOptions) (GetCompletionsResponse, error) { + var err error + req, err := client.getCompletionsCreateRequest(ctx, body, options) + if err != nil { + return GetCompletionsResponse{}, err + } + httpResp, err := client.internal.Pipeline().Do(req) + if err != nil { + return GetCompletionsResponse{}, err + } + if !runtime.HasStatusCode(httpResp, http.StatusOK) { + err = client.newError(httpResp) + return GetCompletionsResponse{}, err + } + resp, err := client.getCompletionsHandleResponse(httpResp) + return resp, err +} + +// getCompletionsCreateRequest creates the GetCompletions request. +func (client *Client) getCompletionsCreateRequest(ctx context.Context, body CompletionsOptions, options *GetCompletionsOptions) (*policy.Request, error) { + urlPath := "completions" + req, err := runtime.NewRequest(ctx, http.MethodPost, client.formatURL(urlPath, getDeploymentID(body))) + if err != nil { + return nil, err + } + reqQP := req.Raw().URL.Query() + reqQP.Set("api-version", "2023-07-01-preview") + req.Raw().URL.RawQuery = reqQP.Encode() + req.Raw().Header["Accept"] = []string{"application/json"} + if err := runtime.MarshalAsJSON(req, body); err != nil { + return nil, err + } + return req, nil +} + +// getCompletionsHandleResponse handles the GetCompletions response. +func (client *Client) getCompletionsHandleResponse(resp *http.Response) (GetCompletionsResponse, error) { + result := GetCompletionsResponse{} + if err := runtime.UnmarshalAsJSON(resp, &result.Completions); err != nil { + return GetCompletionsResponse{}, err + } + return result, nil +} + +// GetEmbeddings - Return the embeddings for a given prompt. +// If the operation fails it returns an *azcore.ResponseError type. +// +// Generated from API version 2023-07-01-preview +// - options - GetEmbeddingsOptions contains the optional parameters for the Client.GetEmbeddings method. +func (client *Client) GetEmbeddings(ctx context.Context, body EmbeddingsOptions, options *GetEmbeddingsOptions) (GetEmbeddingsResponse, error) { + var err error + req, err := client.getEmbeddingsCreateRequest(ctx, body, options) + if err != nil { + return GetEmbeddingsResponse{}, err + } + httpResp, err := client.internal.Pipeline().Do(req) + if err != nil { + return GetEmbeddingsResponse{}, err + } + if !runtime.HasStatusCode(httpResp, http.StatusOK) { + err = client.newError(httpResp) + return GetEmbeddingsResponse{}, err + } + resp, err := client.getEmbeddingsHandleResponse(httpResp) + return resp, err +} + +// getEmbeddingsCreateRequest creates the GetEmbeddings request. +func (client *Client) getEmbeddingsCreateRequest(ctx context.Context, body EmbeddingsOptions, options *GetEmbeddingsOptions) (*policy.Request, error) { + urlPath := "embeddings" + req, err := runtime.NewRequest(ctx, http.MethodPost, client.formatURL(urlPath, getDeploymentID(body))) + if err != nil { + return nil, err + } + reqQP := req.Raw().URL.Query() + reqQP.Set("api-version", "2023-07-01-preview") + req.Raw().URL.RawQuery = reqQP.Encode() + req.Raw().Header["Accept"] = []string{"application/json"} + if err := runtime.MarshalAsJSON(req, body); err != nil { + return nil, err + } + return req, nil +} + +// getEmbeddingsHandleResponse handles the GetEmbeddings response. +func (client *Client) getEmbeddingsHandleResponse(resp *http.Response) (GetEmbeddingsResponse, error) { + result := GetEmbeddingsResponse{} + if err := runtime.UnmarshalAsJSON(resp, &result.Embeddings); err != nil { + return GetEmbeddingsResponse{}, err + } + return result, nil +} diff --git a/sdk/ai/azopenai/client_chat_completions_test.go b/sdk/ai/azopenai/client_chat_completions_test.go new file mode 100644 index 000000000000..7dd168950a5b --- /dev/null +++ b/sdk/ai/azopenai/client_chat_completions_test.go @@ -0,0 +1,233 @@ +//go:build go1.18 +// +build go1.18 + +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +package azopenai_test + +import ( + "context" + "errors" + "io" + "os" + "testing" + + "github.com/Azure/azure-sdk-for-go/sdk/ai/azopenai" + "github.com/Azure/azure-sdk-for-go/sdk/azcore" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/to" + "github.com/Azure/azure-sdk-for-go/sdk/azidentity" + "github.com/Azure/azure-sdk-for-go/sdk/internal/recording" + "github.com/stretchr/testify/require" +) + +func newTestChatCompletionOptions(tv testVars) azopenai.ChatCompletionsOptions { + return azopenai.ChatCompletionsOptions{ + Messages: []azopenai.ChatMessage{ + { + Role: to.Ptr(azopenai.ChatRole("user")), + Content: to.Ptr("Count to 10, with a comma between each number, no newlines and a period at the end. E.g., 1, 2, 3, ..."), + }, + }, + MaxTokens: to.Ptr(int32(1024)), + Temperature: to.Ptr(float32(0.0)), + DeploymentID: tv.ChatCompletions, + } +} + +var expectedContent = "1, 2, 3, 4, 5, 6, 7, 8, 9, 10." +var expectedRole = azopenai.ChatRoleAssistant + +func TestClient_GetChatCompletions(t *testing.T) { + cred, err := azopenai.NewKeyCredential(azureOpenAI.APIKey) + require.NoError(t, err) + + chatClient, err := azopenai.NewClientWithKeyCredential(azureOpenAI.Endpoint, cred, newClientOptionsForTest(t)) + require.NoError(t, err) + + testGetChatCompletions(t, chatClient, azureOpenAI) +} + +func TestClient_GetChatCompletionsStream(t *testing.T) { + chatClient := newAzureOpenAIClientForTest(t, azureOpenAICanary) + testGetChatCompletionsStream(t, chatClient, azureOpenAICanary) +} + +func TestClient_OpenAI_GetChatCompletions(t *testing.T) { + if testing.Short() { + t.Skip("Skipping OpenAI tests when attempting to do quick tests") + } + + chatClient := newOpenAIClientForTest(t) + testGetChatCompletions(t, chatClient, openAI) +} + +func TestClient_OpenAI_GetChatCompletionsStream(t *testing.T) { + if testing.Short() { + t.Skip("Skipping OpenAI tests when attempting to do quick tests") + } + + chatClient := newOpenAIClientForTest(t) + testGetChatCompletionsStream(t, chatClient, openAI) +} + +func testGetChatCompletions(t *testing.T, client *azopenai.Client, tv testVars) { + expected := azopenai.ChatCompletions{ + Choices: []azopenai.ChatChoice{ + { + Message: &azopenai.ChatChoiceMessage{ + Role: &expectedRole, + Content: &expectedContent, + }, + Index: to.Ptr(int32(0)), + FinishReason: to.Ptr(azopenai.CompletionsFinishReason("stop")), + }, + }, + Usage: &azopenai.CompletionsUsage{ + // these change depending on which model you use. These #'s work for gpt-4, which is + // what I'm using for these tests. + CompletionTokens: to.Ptr(int32(29)), + PromptTokens: to.Ptr(int32(42)), + TotalTokens: to.Ptr(int32(71)), + }, + } + + resp, err := client.GetChatCompletions(context.Background(), newTestChatCompletionOptions(tv), nil) + require.NoError(t, err) + + if tv.Azure { + // Azure also provides content-filtering. This particular prompt and responses + // will be considered safe. + expected.PromptAnnotations = []azopenai.PromptFilterResult{ + {PromptIndex: to.Ptr[int32](0), ContentFilterResults: (*azopenai.PromptFilterResultContentFilterResults)(safeContentFilter)}, + } + expected.Choices[0].ContentFilterResults = safeContentFilter + } + + require.NotEmpty(t, resp.ID) + require.NotEmpty(t, resp.Created) + + expected.ID = resp.ID + expected.Created = resp.Created + + require.Equal(t, expected, resp.ChatCompletions) +} + +func testGetChatCompletionsStream(t *testing.T, client *azopenai.Client, tv testVars) { + streamResp, err := client.GetChatCompletionsStream(context.Background(), newTestChatCompletionOptions(tv), nil) + require.NoError(t, err) + + // the data comes back differently for streaming + // 1. the text comes back in the ChatCompletion.Delta field + // 2. the role is only sent on the first streamed ChatCompletion + // check that the role came back as well. + var choices []azopenai.ChatChoice + + for { + completion, err := streamResp.ChatCompletionsStream.Read() + + if errors.Is(err, io.EOF) { + break + } + + require.NoError(t, err) + + if completion.PromptAnnotations != nil { + require.Equal(t, []azopenai.PromptFilterResult{ + {PromptIndex: to.Ptr[int32](0), ContentFilterResults: (*azopenai.PromptFilterResultContentFilterResults)(safeContentFilter)}, + }, completion.PromptAnnotations) + } + + if len(completion.Choices) == 0 { + // you can get empty entries that contain just metadata (ie, prompt annotations) + continue + } + + require.Equal(t, 1, len(completion.Choices)) + choices = append(choices, completion.Choices[0]) + } + + var message string + + for _, choice := range choices { + if choice.Delta.Content == nil { + continue + } + + message += *choice.Delta.Content + } + + require.Equal(t, expectedContent, message, "Ultimately, the same result as GetChatCompletions(), just sent across the .Delta field instead") + require.Equal(t, azopenai.ChatRoleAssistant, expectedRole) +} + +func TestClient_GetChatCompletions_DefaultAzureCredential(t *testing.T) { + if recording.GetRecordMode() == recording.PlaybackMode { + t.Skipf("Not running this test in playback (for now)") + } + + if os.Getenv("USE_TOKEN_CREDS") != "true" { + t.Skipf("USE_TOKEN_CREDS is not true, disabling token credential tests") + } + + recordingTransporter := newRecordingTransporter(t) + + dac, err := azidentity.NewDefaultAzureCredential(&azidentity.DefaultAzureCredentialOptions{ + ClientOptions: policy.ClientOptions{ + Transport: recordingTransporter, + }, + }) + require.NoError(t, err) + + chatClient, err := azopenai.NewClient(azureOpenAI.Endpoint, dac, &azopenai.ClientOptions{ + ClientOptions: policy.ClientOptions{Transport: recordingTransporter}, + }) + require.NoError(t, err) + + testGetChatCompletions(t, chatClient, azureOpenAI) +} + +func TestClient_GetChatCompletions_InvalidModel(t *testing.T) { + cred, err := azopenai.NewKeyCredential(azureOpenAI.APIKey) + require.NoError(t, err) + + chatClient, err := azopenai.NewClientWithKeyCredential(azureOpenAI.Endpoint, cred, newClientOptionsForTest(t)) + require.NoError(t, err) + + _, err = chatClient.GetChatCompletions(context.Background(), azopenai.ChatCompletionsOptions{ + Messages: []azopenai.ChatMessage{ + { + Role: to.Ptr(azopenai.ChatRole("user")), + Content: to.Ptr("Count to 100, with a comma between each number and no newlines. E.g., 1, 2, 3, ..."), + }, + }, + MaxTokens: to.Ptr(int32(1024)), + Temperature: to.Ptr(float32(0.0)), + DeploymentID: "invalid model name", + }, nil) + + var respErr *azcore.ResponseError + require.ErrorAs(t, err, &respErr) + require.Equal(t, "DeploymentNotFound", respErr.ErrorCode) +} + +func TestClient_GetChatCompletionsStream_Error(t *testing.T) { + if recording.GetRecordMode() == recording.PlaybackMode { + t.Skip() + } + + t.Run("AzureOpenAI", func(t *testing.T) { + client := newBogusAzureOpenAIClient(t) + streamResp, err := client.GetChatCompletionsStream(context.Background(), newTestChatCompletionOptions(azureOpenAI), nil) + require.Empty(t, streamResp) + assertResponseIsError(t, err) + }) + + t.Run("OpenAI", func(t *testing.T) { + client := newBogusOpenAIClient(t) + streamResp, err := client.GetChatCompletionsStream(context.Background(), newTestChatCompletionOptions(openAI), nil) + require.Empty(t, streamResp) + assertResponseIsError(t, err) + }) +} diff --git a/sdk/ai/azopenai/client_completions_test.go b/sdk/ai/azopenai/client_completions_test.go new file mode 100644 index 000000000000..41e3191b73d4 --- /dev/null +++ b/sdk/ai/azopenai/client_completions_test.go @@ -0,0 +1,74 @@ +//go:build go1.18 +// +build go1.18 + +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +package azopenai_test + +import ( + "context" + "testing" + + "github.com/Azure/azure-sdk-for-go/sdk/ai/azopenai" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/to" + "github.com/stretchr/testify/require" +) + +func TestClient_GetCompletions_AzureOpenAI(t *testing.T) { + cred, err := azopenai.NewKeyCredential(azureOpenAI.APIKey) + require.NoError(t, err) + + client, err := azopenai.NewClientWithKeyCredential(azureOpenAI.Endpoint, cred, newClientOptionsForTest(t)) + require.NoError(t, err) + + testGetCompletions(t, client, true) +} + +func TestClient_GetCompletions_OpenAI(t *testing.T) { + if testing.Short() { + t.Skip("Skipping OpenAI tests when attempting to do quick tests") + } + + client := newOpenAIClientForTest(t) + testGetCompletions(t, client, false) +} + +func testGetCompletions(t *testing.T, client *azopenai.Client, isAzure bool) { + deploymentID := openAI.Completions + + if isAzure { + deploymentID = azureOpenAI.Completions + } + + resp, err := client.GetCompletions(context.Background(), azopenai.CompletionsOptions{ + Prompt: []string{"What is Azure OpenAI?"}, + MaxTokens: to.Ptr(int32(2048 - 127)), + Temperature: to.Ptr(float32(0.0)), + DeploymentID: deploymentID, + }, nil) + require.NoError(t, err) + + want := azopenai.GetCompletionsResponse{ + Completions: azopenai.Completions{ + Choices: []azopenai.Choice{ + { + Text: to.Ptr("\n\nAzure OpenAI is a platform from Microsoft that provides access to OpenAI's artificial intelligence (AI) technologies. It enables developers to build, train, and deploy AI models in the cloud. Azure OpenAI provides access to OpenAI's powerful AI technologies, such as GPT-3, which can be used to create natural language processing (NLP) applications, computer vision models, and reinforcement learning models."), + Index: to.Ptr(int32(0)), + FinishReason: to.Ptr(azopenai.CompletionsFinishReason("stop")), + LogProbs: nil, + }, + }, + Usage: &azopenai.CompletionsUsage{ + CompletionTokens: to.Ptr(int32(85)), + PromptTokens: to.Ptr(int32(6)), + TotalTokens: to.Ptr(int32(91)), + }, + }, + } + + want.ID = resp.Completions.ID + want.Created = resp.Completions.Created + + require.Equal(t, want, resp) +} diff --git a/sdk/ai/azopenai/client_embeddings_test.go b/sdk/ai/azopenai/client_embeddings_test.go new file mode 100644 index 000000000000..b9d70961ce8d --- /dev/null +++ b/sdk/ai/azopenai/client_embeddings_test.go @@ -0,0 +1,99 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +package azopenai_test + +import ( + "context" + "testing" + + "github.com/Azure/azure-sdk-for-go/sdk/ai/azopenai" + "github.com/Azure/azure-sdk-for-go/sdk/azcore" + "github.com/stretchr/testify/require" +) + +func TestClient_GetEmbeddings_InvalidModel(t *testing.T) { + cred, err := azopenai.NewKeyCredential(azureOpenAI.APIKey) + require.NoError(t, err) + + chatClient, err := azopenai.NewClientWithKeyCredential(azureOpenAI.Endpoint, cred, newClientOptionsForTest(t)) + require.NoError(t, err) + + _, err = chatClient.GetEmbeddings(context.Background(), azopenai.EmbeddingsOptions{ + DeploymentID: "thisdoesntexist", + }, nil) + + var respErr *azcore.ResponseError + require.ErrorAs(t, err, &respErr) + require.Equal(t, "DeploymentNotFound", respErr.ErrorCode) +} + +func TestClient_OpenAI_GetEmbeddings(t *testing.T) { + if testing.Short() { + t.Skip("Skipping OpenAI tests when attempting to do quick tests") + } + + client := newOpenAIClientForTest(t) + testGetEmbeddings(t, client, openAI.Embeddings) +} + +func TestClient_GetEmbeddings(t *testing.T) { + cred, err := azopenai.NewKeyCredential(azureOpenAI.APIKey) + require.NoError(t, err) + + client, err := azopenai.NewClientWithKeyCredential(azureOpenAI.Endpoint, cred, newClientOptionsForTest(t)) + require.NoError(t, err) + + testGetEmbeddings(t, client, azureOpenAI.Embeddings) +} + +func testGetEmbeddings(t *testing.T, client *azopenai.Client, modelOrDeploymentID string) { + type args struct { + ctx context.Context + deploymentID string + body azopenai.EmbeddingsOptions + options *azopenai.GetEmbeddingsOptions + } + + tests := []struct { + name string + client *azopenai.Client + args args + want azopenai.GetEmbeddingsResponse + wantErr bool + }{ + { + name: "Embeddings", + client: client, + args: args{ + ctx: context.TODO(), + deploymentID: modelOrDeploymentID, + body: azopenai.EmbeddingsOptions{ + Input: []string{"\"Your text string goes here\""}, + DeploymentID: modelOrDeploymentID, + }, + options: nil, + }, + want: azopenai.GetEmbeddingsResponse{ + azopenai.Embeddings{ + Data: []azopenai.EmbeddingItem{}, + Usage: &azopenai.EmbeddingsUsage{}, + }, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := tt.client.GetEmbeddings(tt.args.ctx, tt.args.body, tt.args.options) + if (err != nil) != tt.wantErr { + t.Errorf("Client.GetEmbeddings() error = %v, wantErr %v", err, tt.wantErr) + return + } + if len(got.Embeddings.Data[0].Embedding) != 4096 { + t.Errorf("Client.GetEmbeddings() len(Data) want 4096, got %d", len(got.Embeddings.Data)) + return + } + }) + } +} diff --git a/sdk/ai/azopenai/client_functions_test.go b/sdk/ai/azopenai/client_functions_test.go new file mode 100644 index 000000000000..f4203becd538 --- /dev/null +++ b/sdk/ai/azopenai/client_functions_test.go @@ -0,0 +1,94 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +package azopenai_test + +import ( + "context" + "encoding/json" + "testing" + + "github.com/Azure/azure-sdk-for-go/sdk/ai/azopenai" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/to" + "github.com/stretchr/testify/require" +) + +type Params struct { + Type string `json:"type"` + Properties map[string]ParamProperty `json:"properties"` + Required []string `json:"required,omitempty"` +} + +type ParamProperty struct { + Type string `json:"type"` + Description string `json:"description,omitempty"` + Enum []string `json:"enum,omitempty"` +} + +func TestGetChatCompletions_usingFunctions(t *testing.T) { + // https://platform.openai.com/docs/guides/gpt/function-calling + + t.Run("OpenAI", func(t *testing.T) { + chatClient := newOpenAIClientForTest(t) + testChatCompletionsFunctions(t, chatClient, openAI) + }) + + t.Run("AzureOpenAI", func(t *testing.T) { + chatClient := newAzureOpenAIClientForTest(t, azureOpenAI) + testChatCompletionsFunctions(t, chatClient, azureOpenAI) + }) +} + +func testChatCompletionsFunctions(t *testing.T, chatClient *azopenai.Client, tv testVars) { + body := azopenai.ChatCompletionsOptions{ + DeploymentID: tv.ChatCompletions, + Messages: []azopenai.ChatMessage{ + { + Role: to.Ptr(azopenai.ChatRoleUser), + Content: to.Ptr("What's the weather like in Boston, MA, in celsius?"), + }, + }, + FunctionCall: &azopenai.ChatCompletionsOptionsFunctionCall{ + Value: to.Ptr("auto"), + }, + Functions: []azopenai.FunctionDefinition{ + { + Name: to.Ptr("get_current_weather"), + Description: to.Ptr("Get the current weather in a given location"), + Parameters: Params{ + Required: []string{"location"}, + Type: "object", + Properties: map[string]ParamProperty{ + "location": { + Type: "string", + Description: "The city and state, e.g. San Francisco, CA", + }, + "unit": { + Type: "string", + Enum: []string{"celsius", "fahrenheit"}, + }, + }, + }, + }, + }, + Temperature: to.Ptr[float32](0.0), + } + + resp, err := chatClient.GetChatCompletions(context.Background(), body, nil) + require.NoError(t, err) + + funcCall := resp.ChatCompletions.Choices[0].Message.FunctionCall + + require.Equal(t, "get_current_weather", *funcCall.Name) + + type location struct { + Location string `json:"location"` + Unit string `json:"unit"` + } + + var funcParams *location + err = json.Unmarshal([]byte(*funcCall.Arguments), &funcParams) + require.NoError(t, err) + + require.Equal(t, location{Location: "Boston, MA", Unit: "celsius"}, *funcParams) +} diff --git a/sdk/ai/azopenai/client_rai_test.go b/sdk/ai/azopenai/client_rai_test.go new file mode 100644 index 000000000000..2b11be357cec --- /dev/null +++ b/sdk/ai/azopenai/client_rai_test.go @@ -0,0 +1,93 @@ +//go:build go1.18 +// +build go1.18 + +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +package azopenai_test + +import ( + "context" + "testing" + + "github.com/Azure/azure-sdk-for-go/sdk/ai/azopenai" + "github.com/Azure/azure-sdk-for-go/sdk/azcore" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/to" + "github.com/stretchr/testify/require" +) + +func TestClient_GetCompletions_AzureOpenAI_ContentFilter_Response(t *testing.T) { + // Scenario: Your API call asks for multiple responses (N>1) and at least 1 of the responses is filtered + // https://github.com/MicrosoftDocs/azure-docs/blob/main/articles/cognitive-services/openai/concepts/content-filter.md#scenario-your-api-call-asks-for-multiple-responses-n1-and-at-least-1-of-the-responses-is-filtered + client := newAzureOpenAIClientForTest(t, azureOpenAI) + + resp, err := client.GetCompletions(context.Background(), azopenai.CompletionsOptions{ + Prompt: []string{"How do I rob a bank?"}, + MaxTokens: to.Ptr(int32(2048 - 127)), + Temperature: to.Ptr(float32(0.0)), + DeploymentID: azureOpenAI.Completions, + }, nil) + + require.Empty(t, resp) + assertContentFilterError(t, err, false) +} + +func TestClient_GetChatCompletions_AzureOpenAI_ContentFilterWithError(t *testing.T) { + client := newAzureOpenAIClientForTest(t, azureOpenAICanary) + + resp, err := client.GetChatCompletions(context.Background(), azopenai.ChatCompletionsOptions{ + Messages: []azopenai.ChatMessage{ + {Role: to.Ptr(azopenai.ChatRoleSystem), Content: to.Ptr("You are a helpful assistant.")}, + {Role: to.Ptr(azopenai.ChatRoleUser), Content: to.Ptr("How do I rob a bank?")}, + }, + MaxTokens: to.Ptr(int32(2048 - 127)), + Temperature: to.Ptr(float32(0.0)), + DeploymentID: azureOpenAICanary.ChatCompletions, + }, nil) + require.Empty(t, resp) + assertContentFilterError(t, err, true) +} + +func TestClient_GetChatCompletions_AzureOpenAI_ContentFilter_WithResponse(t *testing.T) { + client := newAzureOpenAIClientForTest(t, azureOpenAICanary) + + resp, err := client.GetChatCompletions(context.Background(), azopenai.ChatCompletionsOptions{ + Messages: []azopenai.ChatMessage{ + {Role: to.Ptr(azopenai.ChatRoleUser), Content: to.Ptr("How do I cook a bell pepper?")}, + }, + MaxTokens: to.Ptr(int32(2048 - 127)), + Temperature: to.Ptr(float32(0.0)), + DeploymentID: azureOpenAICanary.ChatCompletions, + }, nil) + + require.NoError(t, err) + + require.Equal(t, safeContentFilter, resp.ChatCompletions.Choices[0].ContentFilterResults) +} + +// assertContentFilterError checks that the content filtering error came back from Azure OpenAI. +func assertContentFilterError(t *testing.T, err error, requireAnnotations bool) { + var respErr *azcore.ResponseError + require.ErrorAs(t, err, &respErr) + require.Equal(t, "content_filter", respErr.ErrorCode) + + require.Contains(t, respErr.Error(), "The response was filtered due to the prompt triggering") + + // Azure also returns error information when content filtering happens. + var contentFilterErr *azopenai.ContentFilterResponseError + require.ErrorAs(t, err, &contentFilterErr) + + if requireAnnotations { + require.Equal(t, &azopenai.ContentFilterResultsHate{Filtered: to.Ptr(false), Severity: to.Ptr(azopenai.ContentFilterSeveritySafe)}, contentFilterErr.ContentFilterResults.Hate) + require.Equal(t, &azopenai.ContentFilterResultsSelfHarm{Filtered: to.Ptr(false), Severity: to.Ptr(azopenai.ContentFilterSeveritySafe)}, contentFilterErr.ContentFilterResults.SelfHarm) + require.Equal(t, &azopenai.ContentFilterResultsSexual{Filtered: to.Ptr(false), Severity: to.Ptr(azopenai.ContentFilterSeveritySafe)}, contentFilterErr.ContentFilterResults.Sexual) + require.Equal(t, &azopenai.ContentFilterResultsViolence{Filtered: to.Ptr(true), Severity: to.Ptr(azopenai.ContentFilterSeverityMedium)}, contentFilterErr.ContentFilterResults.Violence) + } +} + +var safeContentFilter = &azopenai.ChatChoiceContentFilterResults{ + Hate: &azopenai.ContentFilterResultsHate{Filtered: to.Ptr(false), Severity: to.Ptr(azopenai.ContentFilterSeveritySafe)}, + SelfHarm: &azopenai.ContentFilterResultsSelfHarm{Filtered: to.Ptr(false), Severity: to.Ptr(azopenai.ContentFilterSeveritySafe)}, + Sexual: &azopenai.ContentFilterResultsSexual{Filtered: to.Ptr(false), Severity: to.Ptr(azopenai.ContentFilterSeveritySafe)}, + Violence: &azopenai.ContentFilterResultsViolence{Filtered: to.Ptr(false), Severity: to.Ptr(azopenai.ContentFilterSeveritySafe)}, +} diff --git a/sdk/ai/azopenai/client_shared_test.go b/sdk/ai/azopenai/client_shared_test.go new file mode 100644 index 000000000000..bca594c8fe9a --- /dev/null +++ b/sdk/ai/azopenai/client_shared_test.go @@ -0,0 +1,260 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +package azopenai_test + +import ( + "crypto/tls" + "fmt" + "net/http" + "os" + "regexp" + "strings" + "testing" + "time" + + "github.com/Azure/azure-sdk-for-go/sdk/ai/azopenai" + "github.com/Azure/azure-sdk-for-go/sdk/azcore" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy" + "github.com/Azure/azure-sdk-for-go/sdk/internal/recording" + "github.com/joho/godotenv" + "github.com/stretchr/testify/require" +) + +var ( + azureOpenAI testVars + azureOpenAICanary testVars + openAI testVars +) + +type testVars struct { + Endpoint string // env: AOAI_ENDPOINT, OPENAI_ENDPOINT + APIKey string // env: AOAI_API_KEY, OPENAI_API_KEY + Completions string // env: AOAI_COMPLETIONS_MODEL_DEPLOYMENT, OPENAI_COMPLETIONS_MODEL + ChatCompletions string // env: AOAI_CHAT_COMPLETIONS_MODEL_DEPLOYMENT, OPENAI_CHAT_COMPLETIONS_MODEL + Embeddings string // env: AOAI_EMBEDDINGS_MODEL_DEPLOYMENT, OPENAI_EMBEDDINGS_MODEL + Azure bool +} + +func newTestVars(prefix string, isCanary bool) testVars { + getRequired := func(name string) string { + v := os.Getenv(name) + + if v == "" { + panic(fmt.Sprintf("Env variable %s is missing", name)) + } + + return v + } + + azure := prefix == "AOAI" + + canarySuffix := "" + deplSuffix := "" + + if azure { + deplSuffix += "_DEPLOYMENT" + } + + if isCanary { + canarySuffix += "_CANARY" + } + + tv := testVars{ + Endpoint: getRequired(prefix + "_ENDPOINT" + canarySuffix), + APIKey: getRequired(prefix + "_API_KEY" + canarySuffix), + + Completions: getRequired(prefix + "_COMPLETIONS_MODEL" + deplSuffix + canarySuffix), + + // ex: gpt-4-0613 + ChatCompletions: getRequired(prefix + "_CHAT_COMPLETIONS_MODEL" + deplSuffix + canarySuffix), + + // ex: embedding + Embeddings: getRequired(prefix + "_EMBEDDINGS_MODEL" + deplSuffix + canarySuffix), + + Azure: azure, + } + + if tv.Endpoint != "" && !strings.HasSuffix(tv.Endpoint, "/") { + // (this just makes recording replacement easier) + tv.Endpoint += "/" + } + + return tv +} + +const fakeEndpoint = "https://recordedhost/" +const fakeAPIKey = "redacted" + +func initEnvVars() { + if recording.GetRecordMode() == recording.PlaybackMode { + azureOpenAI.Azure = true + azureOpenAI.Endpoint = fakeEndpoint + azureOpenAI.APIKey = fakeAPIKey + openAI.APIKey = fakeAPIKey + openAI.Endpoint = fakeEndpoint + + azureOpenAICanary.Azure = true + azureOpenAICanary.Endpoint = fakeEndpoint + azureOpenAICanary.APIKey = fakeAPIKey + azureOpenAICanary.Completions = "" + azureOpenAICanary.ChatCompletions = "gpt-4" + + azureOpenAI.Completions = "text-davinci-003" + openAI.Completions = "text-davinci-003" + + azureOpenAI.ChatCompletions = "gpt-4-0613" + openAI.ChatCompletions = "gpt-4-0613" + + openAI.Embeddings = "text-similarity-curie-001" + azureOpenAI.Embeddings = "embedding" + } else { + if err := godotenv.Load(); err != nil { + fmt.Printf("Failed to load .env file: %s\n", err) + os.Exit(1) + } + + azureOpenAI = newTestVars("AOAI", false) + azureOpenAICanary = newTestVars("AOAI", true) + openAI = newTestVars("OPENAI", false) + } +} + +func newRecordingTransporter(t *testing.T) policy.Transporter { + transport, err := recording.NewRecordingHTTPClient(t, nil) + require.NoError(t, err) + + err = recording.Start(t, "sdk/cognitiveservices/azopenai/testdata", nil) + require.NoError(t, err) + + if recording.GetRecordMode() != recording.PlaybackMode { + err = recording.AddHeaderRegexSanitizer("Api-Key", fakeAPIKey, "", nil) + require.NoError(t, err) + + err = recording.AddHeaderRegexSanitizer("User-Agent", "fake-user-agent", ".*", nil) + require.NoError(t, err) + + // "RequestUri": "https://openai-shared.openai.azure.com/openai/deployments/text-davinci-003/completions?api-version=2023-03-15-preview", + err = recording.AddURISanitizer(fakeEndpoint, regexp.QuoteMeta(azureOpenAI.Endpoint), nil) + require.NoError(t, err) + + err = recording.AddURISanitizer(fakeEndpoint, regexp.QuoteMeta(azureOpenAICanary.Endpoint), nil) + require.NoError(t, err) + + err = recording.AddURISanitizer("/openai/operations/images/00000000-AAAA-BBBB-CCCC-DDDDDDDDDDDD", "/openai/operations/images/[A-Za-z-0-9]+", nil) + require.NoError(t, err) + + if openAI.Endpoint != "" { + err = recording.AddURISanitizer(fakeEndpoint, regexp.QuoteMeta(openAI.Endpoint), nil) + require.NoError(t, err) + } + } + + t.Cleanup(func() { + err := recording.Stop(t, nil) + require.NoError(t, err) + }) + + return transport +} + +func newClientOptionsForTest(t *testing.T) *azopenai.ClientOptions { + co := &azopenai.ClientOptions{} + + if recording.GetRecordMode() == recording.LiveMode { + keyLogPath := os.Getenv("SSLKEYLOGFILE") + + if keyLogPath == "" { + return nil + } + + keyLogWriter, err := os.OpenFile(keyLogPath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0777) + require.NoError(t, err) + + t.Cleanup(func() { + _ = keyLogWriter.Close() + }) + + tp := http.DefaultTransport.(*http.Transport).Clone() + tp.TLSClientConfig = &tls.Config{ + KeyLogWriter: keyLogWriter, + } + + co.Transport = &http.Client{Transport: tp} + } else { + co.Transport = newRecordingTransporter(t) + } + + return co +} + +// newAzureOpenAIClientForTest can create a client pointing to the "canary" endpoint (basically - leading fixes or features) +// or the current deployed endpoint. +func newAzureOpenAIClientForTest(t *testing.T, tv testVars) *azopenai.Client { + cred, err := azopenai.NewKeyCredential(tv.APIKey) + require.NoError(t, err) + + client, err := azopenai.NewClientWithKeyCredential(tv.Endpoint, cred, newClientOptionsForTest(t)) + require.NoError(t, err) + + return client +} + +func newOpenAIClientForTest(t *testing.T) *azopenai.Client { + if openAI.APIKey == "" { + t.Skipf("OPENAI_API_KEY not defined, skipping OpenAI public endpoint test") + } + + cred, err := azopenai.NewKeyCredential(openAI.APIKey) + require.NoError(t, err) + + // we get rate limited quite a bit. + options := newClientOptionsForTest(t) + + if options == nil { + options = &azopenai.ClientOptions{} + } + + options.Retry = policy.RetryOptions{ + MaxRetries: 60, + RetryDelay: time.Second, + MaxRetryDelay: time.Second, + } + + chatClient, err := azopenai.NewClientForOpenAI(openAI.Endpoint, cred, options) + require.NoError(t, err) + + return chatClient +} + +// newBogusAzureOpenAIClient creates a client that uses an invalid key, which will cause Azure OpenAI to return +// a failure. +func newBogusAzureOpenAIClient(t *testing.T) *azopenai.Client { + cred, err := azopenai.NewKeyCredential("bogus-api-key") + require.NoError(t, err) + + client, err := azopenai.NewClientWithKeyCredential(azureOpenAI.Endpoint, cred, newClientOptionsForTest(t)) + require.NoError(t, err) + return client +} + +// newBogusOpenAIClient creates a client that uses an invalid key, which will cause OpenAI to return +// a failure. +func newBogusOpenAIClient(t *testing.T) *azopenai.Client { + cred, err := azopenai.NewKeyCredential("bogus-api-key") + require.NoError(t, err) + + client, err := azopenai.NewClientForOpenAI(openAI.Endpoint, cred, newClientOptionsForTest(t)) + require.NoError(t, err) + return client +} + +func assertResponseIsError(t *testing.T, err error) { + t.Helper() + + var respErr *azcore.ResponseError + require.ErrorAs(t, err, &respErr) + + // we sometimes get rate limited but (for this kind of test) it's actually okay + require.Truef(t, respErr.StatusCode == http.StatusUnauthorized || respErr.StatusCode == http.StatusTooManyRequests, "An acceptable error comes back (actual: %d)", respErr.StatusCode) +} diff --git a/sdk/ai/azopenai/client_test.go b/sdk/ai/azopenai/client_test.go new file mode 100644 index 000000000000..08b4658bb35a --- /dev/null +++ b/sdk/ai/azopenai/client_test.go @@ -0,0 +1,42 @@ +//go:build go1.18 +// +build go1.18 + +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +package azopenai_test + +import ( + "context" + "net/http" + "testing" + + "github.com/Azure/azure-sdk-for-go/sdk/ai/azopenai" + "github.com/Azure/azure-sdk-for-go/sdk/azcore" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/to" + "github.com/Azure/azure-sdk-for-go/sdk/internal/recording" + "github.com/stretchr/testify/require" +) + +func TestClient_OpenAI_InvalidModel(t *testing.T) { + if recording.GetRecordMode() == recording.PlaybackMode || testing.Short() { + t.Skip() + } + + chatClient := newOpenAIClientForTest(t) + + _, err := chatClient.GetChatCompletions(context.Background(), azopenai.ChatCompletionsOptions{ + Messages: []azopenai.ChatMessage{ + { + Role: to.Ptr(azopenai.ChatRoleSystem), + Content: to.Ptr("hello"), + }, + }, + DeploymentID: "non-existent-model", + }, nil) + + var respErr *azcore.ResponseError + require.ErrorAs(t, err, &respErr) + require.Equal(t, http.StatusNotFound, respErr.StatusCode) + require.Contains(t, respErr.Error(), "The model `non-existent-model` does not exist") +} diff --git a/sdk/ai/azopenai/constants.go b/sdk/ai/azopenai/constants.go new file mode 100644 index 000000000000..1c3925d7c9ed --- /dev/null +++ b/sdk/ai/azopenai/constants.go @@ -0,0 +1,131 @@ +//go:build go1.18 +// +build go1.18 + +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// Code generated by Microsoft (R) AutoRest Code Generator. DO NOT EDIT. +// Changes may cause incorrect behavior and will be lost if the code is regenerated. + +package azopenai + +// azureOpenAIOperationState - The state of a job or item. +type azureOpenAIOperationState string + +const ( + azureOpenAIOperationStateCanceled azureOpenAIOperationState = "canceled" + azureOpenAIOperationStateFailed azureOpenAIOperationState = "failed" + azureOpenAIOperationStateNotRunning azureOpenAIOperationState = "notRunning" + azureOpenAIOperationStateRunning azureOpenAIOperationState = "running" + azureOpenAIOperationStateSucceeded azureOpenAIOperationState = "succeeded" +) + +// ChatRole - A description of the intended purpose of a message within a chat completions interaction. +type ChatRole string + +const ( + ChatRoleAssistant ChatRole = "assistant" + ChatRoleFunction ChatRole = "function" + ChatRoleSystem ChatRole = "system" + ChatRoleUser ChatRole = "user" +) + +// PossibleChatRoleValues returns the possible values for the ChatRole const type. +func PossibleChatRoleValues() []ChatRole { + return []ChatRole{ + ChatRoleAssistant, + ChatRoleFunction, + ChatRoleSystem, + ChatRoleUser, + } +} + +// CompletionsFinishReason - Representation of the manner in which a completions response concluded. +type CompletionsFinishReason string + +const ( + CompletionsFinishReasonContentFilter CompletionsFinishReason = "content_filter" + CompletionsFinishReasonFunctionCall CompletionsFinishReason = "function_call" + CompletionsFinishReasonLength CompletionsFinishReason = "length" + CompletionsFinishReasonStop CompletionsFinishReason = "stop" +) + +// PossibleCompletionsFinishReasonValues returns the possible values for the CompletionsFinishReason const type. +func PossibleCompletionsFinishReasonValues() []CompletionsFinishReason { + return []CompletionsFinishReason{ + CompletionsFinishReasonContentFilter, + CompletionsFinishReasonFunctionCall, + CompletionsFinishReasonLength, + CompletionsFinishReasonStop, + } +} + +// ContentFilterSeverity - Ratings for the intensity and risk level of harmful content. +type ContentFilterSeverity string + +const ( + ContentFilterSeverityHigh ContentFilterSeverity = "high" + ContentFilterSeverityLow ContentFilterSeverity = "low" + ContentFilterSeverityMedium ContentFilterSeverity = "medium" + ContentFilterSeveritySafe ContentFilterSeverity = "safe" +) + +// PossibleContentFilterSeverityValues returns the possible values for the ContentFilterSeverity const type. +func PossibleContentFilterSeverityValues() []ContentFilterSeverity { + return []ContentFilterSeverity{ + ContentFilterSeverityHigh, + ContentFilterSeverityLow, + ContentFilterSeverityMedium, + ContentFilterSeveritySafe, + } +} + +// FunctionCallPreset - The collection of predefined behaviors for handling request-provided function information in a chat +// completions operation. +type FunctionCallPreset string + +const ( + FunctionCallPresetAuto FunctionCallPreset = "auto" + FunctionCallPresetNone FunctionCallPreset = "none" +) + +// PossibleFunctionCallPresetValues returns the possible values for the FunctionCallPreset const type. +func PossibleFunctionCallPresetValues() []FunctionCallPreset { + return []FunctionCallPreset{ + FunctionCallPresetAuto, + FunctionCallPresetNone, + } +} + +// ImageGenerationResponseFormat - The format in which the generated images are returned. +type ImageGenerationResponseFormat string + +const ( + ImageGenerationResponseFormatB64JSON ImageGenerationResponseFormat = "b64_json" + ImageGenerationResponseFormatURL ImageGenerationResponseFormat = "url" +) + +// PossibleImageGenerationResponseFormatValues returns the possible values for the ImageGenerationResponseFormat const type. +func PossibleImageGenerationResponseFormatValues() []ImageGenerationResponseFormat { + return []ImageGenerationResponseFormat{ + ImageGenerationResponseFormatB64JSON, + ImageGenerationResponseFormatURL, + } +} + +// ImageSize - The desired size of the generated images. Must be one of 256x256, 512x512, or 1024x1024. +type ImageSize string + +const ( + ImageSize512x512 ImageSize = "512x512" + ImageSize1024x1024 ImageSize = "1024x1024" + ImageSize256x256 ImageSize = "256x256" +) + +// PossibleImageSizeValues returns the possible values for the ImageSize const type. +func PossibleImageSizeValues() []ImageSize { + return []ImageSize{ + ImageSize512x512, + ImageSize1024x1024, + ImageSize256x256, + } +} diff --git a/sdk/ai/azopenai/custom_client.go b/sdk/ai/azopenai/custom_client.go new file mode 100644 index 000000000000..701fba27797a --- /dev/null +++ b/sdk/ai/azopenai/custom_client.go @@ -0,0 +1,259 @@ +//go:build go1.18 +// +build go1.18 + +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +// Package azopenai Azure OpenAI Service provides access to OpenAI's powerful language models including the GPT-4, +// GPT-35-Turbo, and Embeddings model series, as well as image generation using DALL-E. +// +// The [Client] in this package can be used with Azure OpenAI or OpenAI. +package azopenai + +// this file contains handwritten additions to the generated code + +import ( + "context" + "encoding/json" + "net/http" + "net/url" + + "github.com/Azure/azure-sdk-for-go/sdk/azcore" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime" +) + +const ( + clientName = "azopenai.Client" + tokenScope = "https://cognitiveservices.azure.com/.default" +) + +// Clients + +// ClientOptions contains optional settings for Client. +type ClientOptions struct { + azcore.ClientOptions +} + +// NewClient creates a new instance of Client that connects to an Azure OpenAI endpoint. +// - endpoint - Azure OpenAI service endpoint, for example: https://{your-resource-name}.openai.azure.com +// - credential - used to authorize requests. Usually a credential from [github.com/Azure/azure-sdk-for-go/sdk/azidentity]. +// - options - client options, pass nil to accept the default values. +func NewClient(endpoint string, credential azcore.TokenCredential, options *ClientOptions) (*Client, error) { + if options == nil { + options = &ClientOptions{} + } + + authPolicy := runtime.NewBearerTokenPolicy(credential, []string{tokenScope}, nil) + azcoreClient, err := azcore.NewClient(clientName, version, runtime.PipelineOptions{PerRetry: []policy.Policy{authPolicy}}, &options.ClientOptions) + + if err != nil { + return nil, err + } + + return &Client{ + internal: azcoreClient, + clientData: clientData{ + endpoint: endpoint, + azure: true, + }, + }, nil +} + +// NewClientWithKeyCredential creates a new instance of Client that connects to an Azure OpenAI endpoint. +// - endpoint - Azure OpenAI service endpoint, for example: https://{your-resource-name}.openai.azure.com +// - credential - used to authorize requests with an API Key credential +// - options - client options, pass nil to accept the default values. +func NewClientWithKeyCredential(endpoint string, credential KeyCredential, options *ClientOptions) (*Client, error) { + if options == nil { + options = &ClientOptions{} + } + + authPolicy := newAPIKeyPolicy(credential, "api-key") + azcoreClient, err := azcore.NewClient(clientName, version, runtime.PipelineOptions{PerRetry: []policy.Policy{authPolicy}}, &options.ClientOptions) + if err != nil { + return nil, err + } + + return &Client{ + internal: azcoreClient, + clientData: clientData{ + endpoint: endpoint, + azure: true, + }, + }, nil +} + +// NewClientForOpenAI creates a new instance of Client which connects to the public OpenAI endpoint. +// - endpoint - OpenAI service endpoint, for example: https://api.openai.com/v1 +// - credential - used to authorize requests with an API Key credential +// - options - client options, pass nil to accept the default values. +func NewClientForOpenAI(endpoint string, credential KeyCredential, options *ClientOptions) (*Client, error) { + if options == nil { + options = &ClientOptions{} + } + openAIPolicy := newOpenAIPolicy(credential) + azcoreClient, err := azcore.NewClient(clientName, version, runtime.PipelineOptions{PerRetry: []policy.Policy{openAIPolicy}}, &options.ClientOptions) + if err != nil { + return nil, err + } + + return &Client{ + internal: azcoreClient, + clientData: clientData{ + endpoint: endpoint, + azure: false, + }, + }, nil +} + +// openAIPolicy is an internal pipeline policy to remove the api-version query parameter +type openAIPolicy struct { + cred KeyCredential +} + +// newOpenAIPolicy creates a new instance of openAIPolicy. +// cred: a KeyCredential implementation. +func newOpenAIPolicy(cred KeyCredential) *openAIPolicy { + return &openAIPolicy{cred: cred} +} + +// Do returns a function which adapts a request to target OpenAI. +// Specifically, it removes the api-version query parameter. +func (b *openAIPolicy) Do(req *policy.Request) (*http.Response, error) { + q := req.Raw().URL.Query() + q.Del("api-version") + req.Raw().Header.Set("authorization", "Bearer "+b.cred.apiKey) + return req.Next() +} + +// Methods that return streaming response +type streamCompletionsOptions struct { + // we strip out the 'stream' field from the options exposed to the customer so + // now we need to add it back in. + any + Stream bool `json:"stream"` +} + +func (o streamCompletionsOptions) MarshalJSON() ([]byte, error) { + bytes, err := json.Marshal(o.any) + + if err != nil { + return nil, err + } + + objectMap := make(map[string]any) + err = json.Unmarshal(bytes, &objectMap) + if err != nil { + return nil, err + } + objectMap["stream"] = o.Stream + return json.Marshal(objectMap) +} + +// GetCompletionsStream - Return the completions for a given prompt as a sequence of events. +// If the operation fails it returns an *azcore.ResponseError type. +// - options - GetCompletionsOptions contains the optional parameters for the Client.GetCompletions method. +func (client *Client) GetCompletionsStream(ctx context.Context, body CompletionsOptions, options *GetCompletionsStreamOptions) (GetCompletionsStreamResponse, error) { + req, err := client.getCompletionsCreateRequest(ctx, body, &GetCompletionsOptions{}) + + if err != nil { + return GetCompletionsStreamResponse{}, err + } + + if err := runtime.MarshalAsJSON(req, streamCompletionsOptions{ + any: body, + Stream: true, + }); err != nil { + return GetCompletionsStreamResponse{}, err + } + + runtime.SkipBodyDownload(req) + + resp, err := client.internal.Pipeline().Do(req) + + if err != nil { + return GetCompletionsStreamResponse{}, err + } + + if !runtime.HasStatusCode(resp, http.StatusOK) { + return GetCompletionsStreamResponse{}, runtime.NewResponseError(resp) + } + + return GetCompletionsStreamResponse{ + CompletionsStream: newEventReader[Completions](resp.Body), + }, nil +} + +// GetChatCompletionsStream - Return the chat completions for a given prompt as a sequence of events. +// If the operation fails it returns an *azcore.ResponseError type. +// - options - GetCompletionsOptions contains the optional parameters for the Client.GetCompletions method. +func (client *Client) GetChatCompletionsStream(ctx context.Context, body ChatCompletionsOptions, options *GetChatCompletionsStreamOptions) (GetChatCompletionsStreamResponse, error) { + req, err := client.getChatCompletionsCreateRequest(ctx, body, &GetChatCompletionsOptions{}) + + if err != nil { + return GetChatCompletionsStreamResponse{}, err + } + + if err := runtime.MarshalAsJSON(req, streamCompletionsOptions{ + any: body, + Stream: true, + }); err != nil { + return GetChatCompletionsStreamResponse{}, err + } + + runtime.SkipBodyDownload(req) + + resp, err := client.internal.Pipeline().Do(req) + + if err != nil { + return GetChatCompletionsStreamResponse{}, err + } + + if !runtime.HasStatusCode(resp, http.StatusOK) { + return GetChatCompletionsStreamResponse{}, runtime.NewResponseError(resp) + } + + return GetChatCompletionsStreamResponse{ + ChatCompletionsStream: newEventReader[ChatCompletions](resp.Body), + }, nil +} + +func (client *Client) formatURL(path string, deploymentID string) string { + switch path { + // https://learn.microsoft.com/en-us/azure/cognitive-services/openai/reference#image-generation + case "/images/generations:submit": + return runtime.JoinPaths(client.endpoint, "openai", path) + default: + if client.azure { + escapedDeplID := url.PathEscape(deploymentID) + return runtime.JoinPaths(client.endpoint, "openai", "deployments", escapedDeplID, path) + } + + return runtime.JoinPaths(client.endpoint, path) + } +} + +func (client *Client) newError(resp *http.Response) error { + return newContentFilterResponseError(resp) +} + +type clientData struct { + endpoint string + azure bool +} + +func getDeploymentID[T ChatCompletionsOptions | CompletionsOptions | EmbeddingsOptions | ImageGenerationOptions](v T) string { + switch a := any(v).(type) { + case ChatCompletionsOptions: + return a.DeploymentID + case CompletionsOptions: + return a.DeploymentID + case EmbeddingsOptions: + return a.DeploymentID + case ImageGenerationOptions: + return "" + default: + return "" + } +} diff --git a/sdk/ai/azopenai/custom_client_functions.go b/sdk/ai/azopenai/custom_client_functions.go new file mode 100644 index 000000000000..480f82a862fc --- /dev/null +++ b/sdk/ai/azopenai/custom_client_functions.go @@ -0,0 +1,41 @@ +//go:build go1.18 +// +build go1.18 + +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +package azopenai + +import ( + "encoding/json" + "errors" +) + +// ChatCompletionsOptionsFunctionCall - Controls how the model responds to function calls. "none" means the model does not +// call a function, and responds to the end-user. "auto" means the model can pick between an end-user or calling a +// function. Specifying a particular function via {"name": "my_function"} forces the model to call that function. "none" is +// the default when no functions are present. "auto" is the default if functions +// are present. +type ChatCompletionsOptionsFunctionCall struct { + // IsFunction is true if Value refers to a function name. + IsFunction bool + + // Value is one of: + // - "auto", meaning the model can pick between an end-user or calling a function + // - "none", meaning the model does not call a function, + // - name of a function, in which case [IsFunction] should be set to true. + Value *string +} + +// MarshalJSON implements the json.Marshaller interface for type ChatCompletionsOptionsFunctionCall. +func (c ChatCompletionsOptionsFunctionCall) MarshalJSON() ([]byte, error) { + if c.IsFunction { + if c.Value == nil { + return nil, errors.New("the Value should be the function name to call, not nil") + } + + return json.Marshal(map[string]string{"name": *c.Value}) + } + + return json.Marshal(c.Value) +} diff --git a/sdk/ai/azopenai/custom_client_image.go b/sdk/ai/azopenai/custom_client_image.go new file mode 100644 index 000000000000..d3a702006753 --- /dev/null +++ b/sdk/ai/azopenai/custom_client_image.go @@ -0,0 +1,88 @@ +//go:build go1.18 +// +build go1.18 + +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +package azopenai + +import ( + "context" + "net/http" + + "github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime" +) + +// CreateImageOptions contains the optional parameters for the Client.CreateImage method. +type CreateImageOptions struct { + // placeholder for future optional parameters +} + +// CreateImageResponse contains the response from method Client.CreateImage. +type CreateImageResponse struct { + ImageGenerations +} + +// CreateImage creates an image using the Dall-E API. +func (client *Client) CreateImage(ctx context.Context, body ImageGenerationOptions, options *CreateImageOptions) (CreateImageResponse, error) { + // on Azure the image generation API is a poller. This is a temporary state so we're abstracting it away + // until it becomes a sync endpoint. + if client.azure { + return generateImageWithAzure(client, ctx, body) + } + + return generateImageWithOpenAI(ctx, client, body) +} + +func generateImageWithAzure(client *Client, ctx context.Context, body ImageGenerationOptions) (CreateImageResponse, error) { + resp, err := client.beginAzureBatchImageGeneration(ctx, body, nil) + + if err != nil { + return CreateImageResponse{}, err + } + + v, err := resp.PollUntilDone(ctx, nil) + + if err != nil { + return CreateImageResponse{}, err + } + + return CreateImageResponse{ + ImageGenerations: *v.Result, + }, nil +} + +func generateImageWithOpenAI(ctx context.Context, client *Client, body ImageGenerationOptions) (CreateImageResponse, error) { + urlPath := "/images/generations" + req, err := runtime.NewRequest(ctx, http.MethodPost, client.formatURL(urlPath, "")) + if err != nil { + return CreateImageResponse{}, err + } + reqQP := req.Raw().URL.Query() + req.Raw().URL.RawQuery = reqQP.Encode() + req.Raw().Header["Accept"] = []string{"application/json"} + + if err := runtime.MarshalAsJSON(req, body); err != nil { + return CreateImageResponse{}, err + } + + resp, err := client.internal.Pipeline().Do(req) + + if err != nil { + return CreateImageResponse{}, err + } + + if !runtime.HasStatusCode(resp, http.StatusOK) { + return CreateImageResponse{}, runtime.NewResponseError(resp) + } + + var gens *ImageGenerations + + if err := runtime.UnmarshalAsJSON(resp, &gens); err != nil { + return CreateImageResponse{}, err + } + + return CreateImageResponse{ + ImageGenerations: *gens, + }, err +} diff --git a/sdk/ai/azopenai/custom_client_image_test.go b/sdk/ai/azopenai/custom_client_image_test.go new file mode 100644 index 000000000000..8f9a34390290 --- /dev/null +++ b/sdk/ai/azopenai/custom_client_image_test.go @@ -0,0 +1,118 @@ +//go:build go1.18 +// +build go1.18 + +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +package azopenai_test + +import ( + "bytes" + "context" + "encoding/base64" + "image/png" + "net/http" + "testing" + "time" + + "github.com/Azure/azure-sdk-for-go/sdk/ai/azopenai" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/to" + "github.com/Azure/azure-sdk-for-go/sdk/internal/recording" + "github.com/stretchr/testify/require" +) + +func TestImageGeneration_AzureOpenAI(t *testing.T) { + if recording.GetRecordMode() == recording.PlaybackMode { + t.Skipf("Ignoring poller-based test") + } + + cred, err := azopenai.NewKeyCredential(azureOpenAI.APIKey) + require.NoError(t, err) + + client, err := azopenai.NewClientWithKeyCredential(azureOpenAI.Endpoint, cred, newClientOptionsForTest(t)) + require.NoError(t, err) + + testImageGeneration(t, client, azopenai.ImageGenerationResponseFormatURL) +} + +func TestImageGeneration_OpenAI(t *testing.T) { + if testing.Short() { + t.Skip("Skipping OpenAI tests when attempting to do quick tests") + } + + client := newOpenAIClientForTest(t) + testImageGeneration(t, client, azopenai.ImageGenerationResponseFormatURL) +} + +func TestImageGeneration_AzureOpenAI_WithError(t *testing.T) { + if recording.GetRecordMode() == recording.PlaybackMode { + t.Skip() + } + + client := newBogusAzureOpenAIClient(t) + testImageGenerationFailure(t, client) +} + +func TestImageGeneration_OpenAI_WithError(t *testing.T) { + if testing.Short() { + t.Skip("Skipping OpenAI tests when attempting to do quick tests") + } + + client := newBogusOpenAIClient(t) + testImageGenerationFailure(t, client) +} + +func TestImageGeneration_OpenAI_Base64(t *testing.T) { + if testing.Short() { + t.Skip("Skipping OpenAI tests when attempting to do quick tests") + } + + client := newOpenAIClientForTest(t) + testImageGeneration(t, client, azopenai.ImageGenerationResponseFormatB64JSON) +} + +func testImageGeneration(t *testing.T, client *azopenai.Client, responseFormat azopenai.ImageGenerationResponseFormat) { + ctx, cancel := context.WithTimeout(context.Background(), time.Minute) + defer cancel() + + resp, err := client.CreateImage(ctx, azopenai.ImageGenerationOptions{ + Prompt: to.Ptr("a cat"), + Size: to.Ptr(azopenai.ImageSize256x256), + ResponseFormat: &responseFormat, + }, nil) + require.NoError(t, err) + + if recording.GetRecordMode() == recording.LiveMode { + switch responseFormat { + case azopenai.ImageGenerationResponseFormatURL: + headResp, err := http.DefaultClient.Head(*resp.Data[0].URL) + require.NoError(t, err) + require.Equal(t, http.StatusOK, headResp.StatusCode) + case azopenai.ImageGenerationResponseFormatB64JSON: + pngBytes, err := base64.StdEncoding.DecodeString(*resp.Data[0].Base64Data) + require.NoError(t, err) + require.NotEmpty(t, pngBytes) + + // the bytes here should just be a valid PNG + buff := bytes.NewBuffer(pngBytes) + + // just check that it's a valid PNG + _, err = png.Decode(buff) + require.NoError(t, err) + } + } +} + +func testImageGenerationFailure(t *testing.T, bogusClient *azopenai.Client) { + ctx, cancel := context.WithTimeout(context.Background(), time.Minute) + defer cancel() + + resp, err := bogusClient.CreateImage(ctx, azopenai.ImageGenerationOptions{ + Prompt: to.Ptr("a cat"), + Size: to.Ptr(azopenai.ImageSize256x256), + ResponseFormat: to.Ptr(azopenai.ImageGenerationResponseFormatURL), + }, nil) + require.Empty(t, resp) + + assertResponseIsError(t, err) +} diff --git a/sdk/ai/azopenai/custom_client_test.go b/sdk/ai/azopenai/custom_client_test.go new file mode 100644 index 000000000000..8dc93029ae7e --- /dev/null +++ b/sdk/ai/azopenai/custom_client_test.go @@ -0,0 +1,175 @@ +//go:build go1.18 +// +build go1.18 + +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +package azopenai_test + +import ( + "context" + "io" + "reflect" + "strings" + "testing" + + "github.com/Azure/azure-sdk-for-go/sdk/ai/azopenai" + "github.com/Azure/azure-sdk-for-go/sdk/azcore" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/to" + "github.com/Azure/azure-sdk-for-go/sdk/internal/recording" + "github.com/stretchr/testify/require" +) + +func TestNewClient(t *testing.T) { + type args struct { + endpoint string + credential azcore.TokenCredential + options *azopenai.ClientOptions + } + tests := []struct { + name string + args args + want *azopenai.Client + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := azopenai.NewClient(tt.args.endpoint, tt.args.credential, tt.args.options) + if (err != nil) != tt.wantErr { + t.Errorf("NewClient() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("NewClient() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestNewClientWithKeyCredential(t *testing.T) { + type args struct { + endpoint string + credential azopenai.KeyCredential + options *azopenai.ClientOptions + } + tests := []struct { + name string + args args + want *azopenai.Client + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := azopenai.NewClientWithKeyCredential(tt.args.endpoint, tt.args.credential, tt.args.options) + if (err != nil) != tt.wantErr { + t.Errorf("NewClientWithKeyCredential() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("NewClientWithKeyCredential() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestGetCompletionsStream_AzureOpenAI(t *testing.T) { + cred, err := azopenai.NewKeyCredential(azureOpenAI.APIKey) + require.NoError(t, err) + + client, err := azopenai.NewClientWithKeyCredential(azureOpenAI.Endpoint, cred, newClientOptionsForTest(t)) + require.NoError(t, err) + + testGetCompletionsStream(t, client, azureOpenAI) +} + +func TestGetCompletionsStream_OpenAI(t *testing.T) { + if testing.Short() { + t.Skip("Skipping OpenAI tests when attempting to do quick tests") + } + + client := newOpenAIClientForTest(t) + testGetCompletionsStream(t, client, openAI) +} + +func testGetCompletionsStream(t *testing.T, client *azopenai.Client, tv testVars) { + body := azopenai.CompletionsOptions{ + Prompt: []string{"What is Azure OpenAI?"}, + MaxTokens: to.Ptr(int32(2048)), + Temperature: to.Ptr(float32(0.0)), + DeploymentID: tv.Completions, + } + + response, err := client.GetCompletionsStream(context.TODO(), body, nil) + require.NoError(t, err) + + if err != nil { + t.Errorf("Client.GetCompletionsStream() error = %v", err) + return + } + reader := response.CompletionsStream + defer reader.Close() + + var sb strings.Builder + var eventCount int + + for { + completion, err := reader.Read() + + if err == io.EOF { + break + } + + if completion.PromptAnnotations != nil { + require.Equal(t, []azopenai.PromptFilterResult{ + {PromptIndex: to.Ptr[int32](0), ContentFilterResults: (*azopenai.PromptFilterResultContentFilterResults)(safeContentFilter)}, + }, completion.PromptAnnotations) + } + + eventCount++ + + if err != nil { + t.Errorf("reader.Read() error = %v", err) + return + } + + if len(completion.Choices) > 0 { + sb.WriteString(*completion.Choices[0].Text) + } + } + got := sb.String() + const want = "\n\nAzure OpenAI is a platform from Microsoft that provides access to OpenAI's artificial intelligence (AI) technologies. It enables developers to build, train, and deploy AI models in the cloud. Azure OpenAI provides access to OpenAI's powerful AI technologies, such as GPT-3, which can be used to create natural language processing (NLP) applications, computer vision models, and reinforcement learning models." + + require.Equal(t, want, got) + require.Equal(t, 86, eventCount) +} + +func TestClient_GetCompletions_Error(t *testing.T) { + if recording.GetRecordMode() == recording.PlaybackMode { + t.Skip() + } + + doTest := func(t *testing.T, client *azopenai.Client, model string) { + streamResp, err := client.GetCompletionsStream(context.Background(), azopenai.CompletionsOptions{ + Prompt: []string{"What is Azure OpenAI?"}, + MaxTokens: to.Ptr(int32(2048 - 127)), + Temperature: to.Ptr(float32(0.0)), + DeploymentID: model, + }, nil) + require.Empty(t, streamResp) + assertResponseIsError(t, err) + } + + t.Run("AzureOpenAI", func(t *testing.T) { + client := newBogusAzureOpenAIClient(t) + doTest(t, client, azureOpenAI.Completions) + }) + + t.Run("OpenAI", func(t *testing.T) { + client := newBogusOpenAIClient(t) + doTest(t, client, openAI.Completions) + }) +} diff --git a/sdk/ai/azopenai/custom_models.go b/sdk/ai/azopenai/custom_models.go new file mode 100644 index 000000000000..8a639b6ecc85 --- /dev/null +++ b/sdk/ai/azopenai/custom_models.go @@ -0,0 +1,97 @@ +//go:build go1.18 +// +build go1.18 + +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +package azopenai + +import ( + "encoding/json" + "net/http" + + "github.com/Azure/azure-sdk-for-go/sdk/azcore" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime" +) + +// Models for methods that return streaming response + +// GetCompletionsStreamOptions contains the optional parameters for the [Client.GetCompletionsStream] method. +type GetCompletionsStreamOptions struct { + // placeholder for future optional parameters +} + +// GetCompletionsStreamResponse is the response from [Client.GetCompletionsStream]. +type GetCompletionsStreamResponse struct { + // CompletionsStream returns the stream of completions. Token limits and other settings may limit the number of completions returned by the service. + CompletionsStream *EventReader[Completions] +} + +// GetChatCompletionsStreamOptions contains the optional parameters for the [Client.GetChatCompletionsStream] method. +type GetChatCompletionsStreamOptions struct { + // placeholder for future optional parameters +} + +// GetChatCompletionsStreamResponse is the response from [Client.GetChatCompletionsStream]. +type GetChatCompletionsStreamResponse struct { + // ChatCompletionsStream returns the stream of completions. Token limits and other settings may limit the number of chat completions returned by the service. + ChatCompletionsStream *EventReader[ChatCompletions] +} + +// ImageGenerationsDataItem contains the results of image generation. +// +// The field that's set will be based on [ImageGenerationOptions.ResponseFormat] and +// are mutually exclusive. +type ImageGenerationsDataItem struct { + // Base64Data is set to image data, encoded as a base64 string, if [ImageGenerationOptions.ResponseFormat] + // was set to [ImageGenerationResponseFormatB64JSON]. + Base64Data *string `json:"b64_json"` + + // URL is the address of a generated image if [ImageGenerationOptions.ResponseFormat] was set + // to [ImageGenerationResponseFormatURL]. + URL *string `json:"url"` +} + +// ContentFilterResponseError is an error as a result of a request being filtered. +type ContentFilterResponseError struct { + azcore.ResponseError + + // ContentFilterResults contains Information about the content filtering category, if it has been detected. + ContentFilterResults *ContentFilterResults +} + +// Unwrap returns the inner error for this error. +func (e *ContentFilterResponseError) Unwrap() error { + return &e.ResponseError +} + +func newContentFilterResponseError(resp *http.Response) error { + respErr := runtime.NewResponseError(resp).(*azcore.ResponseError) + + if respErr.ErrorCode != "content_filter" { + return respErr + } + + body, err := runtime.Payload(resp) + + if err != nil { + return err + } + + var envelope *struct { + Error struct { + InnerError struct { + FilterResult *ContentFilterResults `json:"content_filter_result"` + } `json:"innererror"` + } + } + + if err := json.Unmarshal(body, &envelope); err != nil { + return err + } + + return &ContentFilterResponseError{ + ResponseError: *respErr, + ContentFilterResults: envelope.Error.InnerError.FilterResult, + } +} diff --git a/sdk/ai/azopenai/custom_models_test.go b/sdk/ai/azopenai/custom_models_test.go new file mode 100644 index 000000000000..909a2c6dcd58 --- /dev/null +++ b/sdk/ai/azopenai/custom_models_test.go @@ -0,0 +1,66 @@ +//go:build go1.18 +// +build go1.18 + +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +package azopenai + +import ( + "bytes" + "io" + "net/http" + "net/url" + "os" + "testing" + + "github.com/Azure/azure-sdk-for-go/sdk/azcore" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/to" + "github.com/stretchr/testify/require" +) + +func TestParseResponseError(t *testing.T) { + bodyBytes, err := os.ReadFile("testdata/content_filter_response_error.json") + require.NoError(t, err) + + buff := bytes.NewBuffer(bodyBytes) + + fakeURL, err := url.Parse("https://openai-something.microsoft.com") + require.NoError(t, err) + + resp := &http.Response{ + StatusCode: http.StatusBadRequest, + Body: io.NopCloser(buff), + Request: &http.Request{ + Method: "POST", + URL: fakeURL, + }, + } + + err = newContentFilterResponseError(resp) + + // this is the outer error, which is the standard Azure response error. + var respErr *azcore.ResponseError + require.ErrorAs(t, err, &respErr) + require.Equal(t, http.StatusBadRequest, respErr.StatusCode) + require.Equal(t, "content_filter", respErr.ErrorCode) + + // Azure also returns error information when content filtering happens. + var contentFilterErr *ContentFilterResponseError + require.ErrorAs(t, err, &contentFilterErr) + + // we're still a response error + require.Equal(t, http.StatusBadRequest, respErr.StatusCode) + require.Equal(t, "content_filter", respErr.ErrorCode) + + contentFilterResults := contentFilterErr.ContentFilterResults + + // thsi comment was considered violent, so it was filtered. + require.Equal(t, &ContentFilterResultsViolence{ + Filtered: to.Ptr(true), + Severity: to.Ptr(ContentFilterSeverityMedium)}, contentFilterResults.Violence) + + require.Equal(t, &ContentFilterResultsHate{Filtered: to.Ptr(false), Severity: to.Ptr(ContentFilterSeveritySafe)}, contentFilterResults.Hate) + require.Equal(t, &ContentFilterResultsSelfHarm{Filtered: to.Ptr(false), Severity: to.Ptr(ContentFilterSeveritySafe)}, contentFilterResults.SelfHarm) + require.Equal(t, &ContentFilterResultsSexual{Filtered: to.Ptr(false), Severity: to.Ptr(ContentFilterSeveritySafe)}, contentFilterResults.Sexual) +} diff --git a/sdk/ai/azopenai/event_reader.go b/sdk/ai/azopenai/event_reader.go new file mode 100644 index 000000000000..28aea580e849 --- /dev/null +++ b/sdk/ai/azopenai/event_reader.go @@ -0,0 +1,63 @@ +//go:build go1.18 +// +build go1.18 + +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +package azopenai + +import ( + "bufio" + "encoding/json" + "errors" + "io" + "strings" +) + +// EventReader streams events dynamically from an OpenAI endpoint. +type EventReader[T any] struct { + reader io.Reader // Required for Closing + scanner *bufio.Scanner +} + +func newEventReader[T any](r io.Reader) *EventReader[T] { + return &EventReader[T]{reader: r, scanner: bufio.NewScanner(r)} +} + +// Read reads the next event from the stream. +// Returns io.EOF when there are no further events. +func (er *EventReader[T]) Read() (T, error) { + // https://html.spec.whatwg.org/multipage/server-sent-events.html + for er.scanner.Scan() { // Scan while no error + line := er.scanner.Text() // Get the line & interpret the event stream: + + if line == "" || line[0] == ':' { // If the line is blank or is a comment, skip it + continue + } + + if strings.Contains(line, ":") { // If the line contains a U+003A COLON character (:), process the field + tokens := strings.SplitN(line, ":", 2) + tokens[0], tokens[1] = strings.TrimSpace(tokens[0]), strings.TrimSpace(tokens[1]) + var data T + switch tokens[0] { + case "data": // return the deserialized JSON object + if tokens[1] == "[DONE]" { // If data is [DONE], end of stream was reached + return data, io.EOF + } + err := json.Unmarshal([]byte(tokens[1]), &data) + return data, err + default: // Any other event type is an unexpected + return data, errors.New("Unexpected event type: " + tokens[0]) + } + // Unreachable + } + } + return *new(T), er.scanner.Err() +} + +// Close closes the EventReader and any applicable inner stream state. +func (er *EventReader[T]) Close() { + if closer, ok := er.reader.(io.Closer); ok { + closer.Close() + } +} diff --git a/sdk/ai/azopenai/event_reader_test.go b/sdk/ai/azopenai/event_reader_test.go new file mode 100644 index 000000000000..0644c6b43b91 --- /dev/null +++ b/sdk/ai/azopenai/event_reader_test.go @@ -0,0 +1,42 @@ +//go:build go1.18 +// +build go1.18 + +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +package azopenai + +import ( + "io" + "strings" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestEventReader_InvalidType(t *testing.T) { + data := []string{ + "invaliddata: {\u0022name\u0022:\u0022chatcmpl-7Z4kUpXX6HN85cWY28IXM4EwemLU3\u0022,\u0022object\u0022:\u0022chat.completion.chunk\u0022,\u0022created\u0022:1688594090,\u0022model\u0022:\u0022gpt-4-0613\u0022,\u0022choices\u0022:[{\u0022index\u0022:0,\u0022delta\u0022:{\u0022role\u0022:\u0022assistant\u0022,\u0022content\u0022:\u0022\u0022},\u0022finish_reason\u0022:null}]}\n\n", + } + + text := strings.NewReader(strings.Join(data, "\n")) + eventReader := newEventReader[ChatCompletions](text) + + firstEvent, err := eventReader.Read() + require.Empty(t, firstEvent) + require.EqualError(t, err, "Unexpected event type: invaliddata") +} + +type badReader struct{} + +func (br badReader) Read(p []byte) (n int, err error) { + return 0, io.ErrClosedPipe +} + +func TestEventReader_BadReader(t *testing.T) { + eventReader := newEventReader[ChatCompletions](badReader{}) + + firstEvent, err := eventReader.Read() + require.Empty(t, firstEvent) + require.ErrorIs(t, io.ErrClosedPipe, err) +} diff --git a/sdk/ai/azopenai/example_client_createimage_test.go b/sdk/ai/azopenai/example_client_createimage_test.go new file mode 100644 index 000000000000..aaf3c8b3b5db --- /dev/null +++ b/sdk/ai/azopenai/example_client_createimage_test.go @@ -0,0 +1,66 @@ +//go:build go1.18 +// +build go1.18 + +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +package azopenai_test + +import ( + "context" + "fmt" + "net/http" + "os" + + "github.com/Azure/azure-sdk-for-go/sdk/ai/azopenai" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/to" +) + +func ExampleClient_CreateImage() { + azureOpenAIKey := os.Getenv("AOAI_API_KEY") + + // Ex: "https://.openai.azure.com" + azureOpenAIEndpoint := os.Getenv("AOAI_ENDPOINT") + + if azureOpenAIKey == "" || azureOpenAIEndpoint == "" { + fmt.Fprintf(os.Stderr, "Skipping example, environment variables missing\n") + return + } + + keyCredential, err := azopenai.NewKeyCredential(azureOpenAIKey) + + if err != nil { + // TODO: handle error + } + + client, err := azopenai.NewClientWithKeyCredential(azureOpenAIEndpoint, keyCredential, nil) + + if err != nil { + // TODO: handle error + } + + resp, err := client.CreateImage(context.TODO(), azopenai.ImageGenerationOptions{ + Prompt: to.Ptr("a cat"), + ResponseFormat: to.Ptr(azopenai.ImageGenerationResponseFormatURL), + }, nil) + + if err != nil { + // TODO: handle error + } + + for _, generatedImage := range resp.Data { + // the underlying type for the generatedImage is dictated by the value of + // ImageGenerationOptions.ResponseFormat. In this example we used `azopenai.ImageGenerationResponseFormatURL`, + // so the underlying type will be ImageLocation. + + resp, err := http.Head(*generatedImage.URL) + + if err != nil { + // TODO: handle error + } + + fmt.Fprintf(os.Stderr, "Image generated, HEAD request on URL returned %d\n", resp.StatusCode) + } + + // Output: +} diff --git a/sdk/ai/azopenai/example_client_embeddings_test.go b/sdk/ai/azopenai/example_client_embeddings_test.go new file mode 100644 index 000000000000..78ed1a3d8417 --- /dev/null +++ b/sdk/ai/azopenai/example_client_embeddings_test.go @@ -0,0 +1,55 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +package azopenai_test + +import ( + "context" + "fmt" + "os" + + "github.com/Azure/azure-sdk-for-go/sdk/ai/azopenai" +) + +func ExampleClient_GetEmbeddings() { + azureOpenAIKey := os.Getenv("AOAI_API_KEY") + modelDeploymentID := os.Getenv("AOAI_EMBEDDINGS_MODEL_DEPLOYMENT") + + // Ex: "https://.openai.azure.com" + azureOpenAIEndpoint := os.Getenv("AOAI_ENDPOINT") + + if azureOpenAIKey == "" || modelDeploymentID == "" || azureOpenAIEndpoint == "" { + fmt.Fprintf(os.Stderr, "Skipping example, environment variables missing\n") + return + } + + keyCredential, err := azopenai.NewKeyCredential(azureOpenAIKey) + + if err != nil { + // TODO: handle error + } + + // In Azure OpenAI you must deploy a model before you can use it in your client. For more information + // see here: https://learn.microsoft.com/azure/cognitive-services/openai/how-to/create-resource + client, err := azopenai.NewClientWithKeyCredential(azureOpenAIEndpoint, keyCredential, nil) + + if err != nil { + // TODO: handle error + } + + resp, err := client.GetEmbeddings(context.TODO(), azopenai.EmbeddingsOptions{ + Input: []string{"The food was delicious and the waiter..."}, + DeploymentID: modelDeploymentID, + }, nil) + + if err != nil { + // TODO: handle error + } + + for _, embed := range resp.Data { + // embed.Embedding contains the embeddings for this input index. + fmt.Fprintf(os.Stderr, "Got embeddings for input %d\n", *embed.Index) + } + + // Output: +} diff --git a/sdk/ai/azopenai/example_client_getchatcompletions_test.go b/sdk/ai/azopenai/example_client_getchatcompletions_test.go new file mode 100644 index 000000000000..950c625c246a --- /dev/null +++ b/sdk/ai/azopenai/example_client_getchatcompletions_test.go @@ -0,0 +1,270 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +package azopenai_test + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "io" + "os" + + "github.com/Azure/azure-sdk-for-go/sdk/ai/azopenai" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/to" +) + +func ExampleClient_GetChatCompletions() { + azureOpenAIKey := os.Getenv("AOAI_API_KEY") + modelDeploymentID := os.Getenv("AOAI_CHAT_COMPLETIONS_MODEL_DEPLOYMENT") + + // Ex: "https://.openai.azure.com" + azureOpenAIEndpoint := os.Getenv("AOAI_ENDPOINT") + + if azureOpenAIKey == "" || modelDeploymentID == "" || azureOpenAIEndpoint == "" { + fmt.Fprintf(os.Stderr, "Skipping example, environment variables missing\n") + return + } + + keyCredential, err := azopenai.NewKeyCredential(azureOpenAIKey) + + if err != nil { + // TODO: handle error + } + + // In Azure OpenAI you must deploy a model before you can use it in your client. For more information + // see here: https://learn.microsoft.com/azure/cognitive-services/openai/how-to/create-resource + client, err := azopenai.NewClientWithKeyCredential(azureOpenAIEndpoint, keyCredential, nil) + + if err != nil { + // TODO: handle error + } + + // This is a conversation in progress. + // NOTE: all messages, regardless of role, count against token usage for this API. + messages := []azopenai.ChatMessage{ + // You set the tone and rules of the conversation with a prompt as the system role. + {Role: to.Ptr(azopenai.ChatRoleSystem), Content: to.Ptr("You are a helpful assistant. You will talk like a pirate.")}, + + // The user asks a question + {Role: to.Ptr(azopenai.ChatRoleUser), Content: to.Ptr("Can you help me?")}, + + // The reply would come back from the ChatGPT. You'd add it to the conversation so we can maintain context. + {Role: to.Ptr(azopenai.ChatRoleAssistant), Content: to.Ptr("Arrrr! Of course, me hearty! What can I do for ye?")}, + + // The user answers the question based on the latest reply. + {Role: to.Ptr(azopenai.ChatRoleUser), Content: to.Ptr("What's the best way to train a parrot?")}, + + // from here you'd keep iterating, sending responses back from ChatGPT + } + + gotReply := false + + resp, err := client.GetChatCompletions(context.TODO(), azopenai.ChatCompletionsOptions{ + // This is a conversation in progress. + // NOTE: all messages count against token usage for this API. + Messages: messages, + DeploymentID: modelDeploymentID, + }, nil) + + if err != nil { + // TODO: handle error + } + + for _, choice := range resp.Choices { + gotReply = true + fmt.Fprintf(os.Stderr, "Content[%d]: %s\n", *choice.Index, *choice.Message.Content) + } + + if gotReply { + fmt.Fprintf(os.Stderr, "Got chat completions reply\n") + } + + // Output: +} + +func ExampleClient_GetChatCompletions_functions() { + azureOpenAIKey := os.Getenv("AOAI_API_KEY") + modelDeploymentID := os.Getenv("AOAI_CHAT_COMPLETIONS_MODEL_DEPLOYMENT") + + // Ex: "https://.openai.azure.com" + azureOpenAIEndpoint := os.Getenv("AOAI_ENDPOINT") + + if azureOpenAIKey == "" || modelDeploymentID == "" || azureOpenAIEndpoint == "" { + fmt.Fprintf(os.Stderr, "Skipping example, environment variables missing\n") + return + } + + keyCredential, err := azopenai.NewKeyCredential(azureOpenAIKey) + + if err != nil { + // TODO: handle error + } + + // In Azure OpenAI you must deploy a model before you can use it in your client. For more information + // see here: https://learn.microsoft.com/azure/cognitive-services/openai/how-to/create-resource + client, err := azopenai.NewClientWithKeyCredential(azureOpenAIEndpoint, keyCredential, nil) + + if err != nil { + // TODO: handle error + } + + resp, err := client.GetChatCompletions(context.Background(), azopenai.ChatCompletionsOptions{ + DeploymentID: modelDeploymentID, + Messages: []azopenai.ChatMessage{ + { + Role: to.Ptr(azopenai.ChatRoleUser), + Content: to.Ptr("What's the weather like in Boston, MA, in celsius?"), + }, + }, + FunctionCall: &azopenai.ChatCompletionsOptionsFunctionCall{ + Value: to.Ptr("auto"), + }, + Functions: []azopenai.FunctionDefinition{ + { + Name: to.Ptr("get_current_weather"), + Description: to.Ptr("Get the current weather in a given location"), + + Parameters: map[string]any{ + "required": []string{"location"}, + "type": "object", + "properties": map[string]any{ + "location": map[string]any{ + "type": "string", + "description": "The city and state, e.g. San Francisco, CA", + }, + "unit": map[string]any{ + "type": "string", + "enum": []string{"celsius", "fahrenheit"}, + }, + }, + }, + }, + }, + Temperature: to.Ptr[float32](0.0), + }, nil) + + if err != nil { + // TODO: handle error + } + + funcCall := resp.ChatCompletions.Choices[0].Message.FunctionCall + + // This is the function name we gave in the call to GetCompletions + // Prints: Function name: "get_current_weather" + fmt.Fprintf(os.Stderr, "Function name: %q\n", *funcCall.Name) + + // The arguments for your function come back as a JSON string + var funcParams *struct { + Location string `json:"location"` + Unit string `json:"unit"` + } + err = json.Unmarshal([]byte(*funcCall.Arguments), &funcParams) + + if err != nil { + // TODO: handle error + } + + // Prints: + // Parameters: azopenai_test.location{Location:"Boston, MA", Unit:"celsius"} + fmt.Fprintf(os.Stderr, "Parameters: %#v\n", *funcParams) + + // Output: +} + +func ExampleClient_GetChatCompletionsStream() { + azureOpenAIKey := os.Getenv("AOAI_API_KEY") + modelDeploymentID := os.Getenv("AOAI_CHAT_COMPLETIONS_MODEL_DEPLOYMENT") + + // Ex: "https://.openai.azure.com" + azureOpenAIEndpoint := os.Getenv("AOAI_ENDPOINT") + + if azureOpenAIKey == "" || modelDeploymentID == "" || azureOpenAIEndpoint == "" { + fmt.Fprintf(os.Stderr, "Skipping example, environment variables missing\n") + return + } + + keyCredential, err := azopenai.NewKeyCredential(azureOpenAIKey) + + if err != nil { + // TODO: handle error + } + + // In Azure OpenAI you must deploy a model before you can use it in your client. For more information + // see here: https://learn.microsoft.com/azure/cognitive-services/openai/how-to/create-resource + client, err := azopenai.NewClientWithKeyCredential(azureOpenAIEndpoint, keyCredential, nil) + + if err != nil { + // TODO: handle error + } + + // This is a conversation in progress. + // NOTE: all messages, regardless of role, count against token usage for this API. + messages := []azopenai.ChatMessage{ + // You set the tone and rules of the conversation with a prompt as the system role. + {Role: to.Ptr(azopenai.ChatRoleSystem), Content: to.Ptr("You are a helpful assistant. You will talk like a pirate and limit your responses to 20 words or less.")}, + + // The user asks a question + {Role: to.Ptr(azopenai.ChatRoleUser), Content: to.Ptr("Can you help me?")}, + + // The reply would come back from the ChatGPT. You'd add it to the conversation so we can maintain context. + {Role: to.Ptr(azopenai.ChatRoleAssistant), Content: to.Ptr("Arrrr! Of course, me hearty! What can I do for ye?")}, + + // The user answers the question based on the latest reply. + {Role: to.Ptr(azopenai.ChatRoleUser), Content: to.Ptr("What's the best way to train a parrot?")}, + + // from here you'd keep iterating, sending responses back from ChatGPT + } + + resp, err := client.GetChatCompletionsStream(context.TODO(), azopenai.ChatCompletionsOptions{ + // This is a conversation in progress. + // NOTE: all messages count against token usage for this API. + Messages: messages, + N: to.Ptr[int32](1), + DeploymentID: modelDeploymentID, + }, nil) + + if err != nil { + // TODO: handle error + } + + streamReader := resp.ChatCompletionsStream + gotReply := false + + for { + chatCompletions, err := streamReader.Read() + + if errors.Is(err, io.EOF) { + break + } + + if err != nil { + // TODO: handle error + } + + for _, choice := range chatCompletions.Choices { + gotReply = true + + text := "" + + if choice.Delta.Content != nil { + text = *choice.Delta.Content + } + + role := "" + + if choice.Delta.Role != nil { + role = string(*choice.Delta.Role) + } + + fmt.Fprintf(os.Stderr, "Content[%d], role %q: %q\n", *choice.Index, role, text) + } + } + + if gotReply { + fmt.Fprintf(os.Stderr, "Got chat completions streaming reply\n") + } + + // Output: +} diff --git a/sdk/ai/azopenai/example_client_getcompletions_test.go b/sdk/ai/azopenai/example_client_getcompletions_test.go new file mode 100644 index 000000000000..8b050d148ef1 --- /dev/null +++ b/sdk/ai/azopenai/example_client_getcompletions_test.go @@ -0,0 +1,116 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +package azopenai_test + +import ( + "context" + "errors" + "fmt" + "io" + "os" + + "github.com/Azure/azure-sdk-for-go/sdk/ai/azopenai" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/to" +) + +func ExampleClient_GetCompletions() { + azureOpenAIKey := os.Getenv("AOAI_API_KEY") + modelDeploymentID := os.Getenv("AOAI_COMPLETIONS_MODEL_DEPLOYMENT") + + // Ex: "https://.openai.azure.com" + azureOpenAIEndpoint := os.Getenv("AOAI_ENDPOINT") + + if azureOpenAIKey == "" || modelDeploymentID == "" || azureOpenAIEndpoint == "" { + fmt.Fprintf(os.Stderr, "Skipping example, environment variables missing\n") + return + } + + keyCredential, err := azopenai.NewKeyCredential(azureOpenAIKey) + + if err != nil { + // TODO: handle error + } + + // In Azure OpenAI you must deploy a model before you can use it in your client. For more information + // see here: https://learn.microsoft.com/azure/cognitive-services/openai/how-to/create-resource + client, err := azopenai.NewClientWithKeyCredential(azureOpenAIEndpoint, keyCredential, nil) + + if err != nil { + // TODO: handle error + } + + resp, err := client.GetCompletions(context.TODO(), azopenai.CompletionsOptions{ + Prompt: []string{"What is Azure OpenAI, in 20 words or less"}, + MaxTokens: to.Ptr(int32(2048)), + Temperature: to.Ptr(float32(0.0)), + DeploymentID: modelDeploymentID, + }, nil) + + if err != nil { + // TODO: handle error + } + + for _, choice := range resp.Choices { + fmt.Fprintf(os.Stderr, "Result: %s\n", *choice.Text) + } + + // Output: +} + +func ExampleClient_GetCompletionsStream() { + azureOpenAIKey := os.Getenv("AOAI_API_KEY") + modelDeploymentID := os.Getenv("AOAI_COMPLETIONS_MODEL_DEPLOYMENT") + + // Ex: "https://.openai.azure.com" + azureOpenAIEndpoint := os.Getenv("AOAI_ENDPOINT") + + if azureOpenAIKey == "" || modelDeploymentID == "" || azureOpenAIEndpoint == "" { + fmt.Fprintf(os.Stderr, "Skipping example, environment variables missing\n") + return + } + + keyCredential, err := azopenai.NewKeyCredential(azureOpenAIKey) + + if err != nil { + // TODO: handle error + } + + // In Azure OpenAI you must deploy a model before you can use it in your client. For more information + // see here: https://learn.microsoft.com/azure/cognitive-services/openai/how-to/create-resource + client, err := azopenai.NewClientWithKeyCredential(azureOpenAIEndpoint, keyCredential, nil) + + if err != nil { + // TODO: handle error + } + + resp, err := client.GetCompletionsStream(context.TODO(), azopenai.CompletionsOptions{ + Prompt: []string{"What is Azure OpenAI, in 20 words or less?"}, + MaxTokens: to.Ptr(int32(2048)), + Temperature: to.Ptr(float32(0.0)), + DeploymentID: modelDeploymentID, + }, nil) + + if err != nil { + // TODO: handle error + } + + for { + entry, err := resp.CompletionsStream.Read() + + if errors.Is(err, io.EOF) { + fmt.Fprintf(os.Stderr, "\n*** No more completions ***\n") + break + } + + if err != nil { + // TODO: handle error + } + + for _, choice := range entry.Choices { + fmt.Fprintf(os.Stderr, "Result: %s\n", *choice.Text) + } + } + + // Output: +} diff --git a/sdk/ai/azopenai/example_client_test.go b/sdk/ai/azopenai/example_client_test.go new file mode 100644 index 000000000000..ad09cf6fcacd --- /dev/null +++ b/sdk/ai/azopenai/example_client_test.go @@ -0,0 +1,63 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +package azopenai_test + +import ( + "github.com/Azure/azure-sdk-for-go/sdk/ai/azopenai" + "github.com/Azure/azure-sdk-for-go/sdk/azidentity" +) + +func ExampleNewClientForOpenAI() { + keyCredential, err := azopenai.NewKeyCredential("") + + if err != nil { + // TODO: handle error + } + + // NOTE: this constructor creates a client that connects to the public OpenAI endpoint. + // To connect to an Azure OpenAI endpoint, use azopenai.NewClient() or azopenai.NewClientWithyKeyCredential. + client, err := azopenai.NewClientForOpenAI("https://api.openai.com/v1", keyCredential, nil) + + if err != nil { + // TODO: handle error + } + + _ = client +} + +func ExampleNewClient() { + dac, err := azidentity.NewDefaultAzureCredential(nil) + + if err != nil { + // TODO: handle error + } + + // NOTE: this constructor creates a client that connects to an Azure OpenAI endpoint. + // To connect to the public OpenAI endpoint, use azopenai.NewClientForOpenAI + client, err := azopenai.NewClient("https://.openai.azure.com", dac, nil) + + if err != nil { + // TODO: handle error + } + + _ = client +} + +func ExampleNewClientWithKeyCredential() { + keyCredential, err := azopenai.NewKeyCredential("") + + if err != nil { + // TODO: handle error + } + + // NOTE: this constructor creates a client that connects to an Azure OpenAI endpoint. + // To connect to the public OpenAI endpoint, use azopenai.NewClientForOpenAI + client, err := azopenai.NewClientWithKeyCredential("https://.openai.azure.com", keyCredential, nil) + + if err != nil { + // TODO: handle error + } + + _ = client +} diff --git a/sdk/ai/azopenai/genopenapi3.ps1 b/sdk/ai/azopenai/genopenapi3.ps1 new file mode 100644 index 000000000000..4b4920a614fc --- /dev/null +++ b/sdk/ai/azopenai/genopenapi3.ps1 @@ -0,0 +1,5 @@ +Push-Location ./testdata +npm install +npm run pull +npm run build +Pop-Location \ No newline at end of file diff --git a/sdk/ai/azopenai/go.mod b/sdk/ai/azopenai/go.mod new file mode 100644 index 000000000000..f885e664080f --- /dev/null +++ b/sdk/ai/azopenai/go.mod @@ -0,0 +1,28 @@ +module github.com/Azure/azure-sdk-for-go/sdk/ai/azopenai + +go 1.18 + +require ( + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.6.1 + github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0 + github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0 + github.com/joho/godotenv v1.3.0 + github.com/stretchr/testify v1.7.0 +) + +require ( + github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/dnaeon/go-vcr v1.2.0 // indirect + github.com/golang-jwt/jwt/v4 v4.5.0 // indirect + github.com/google/uuid v1.3.0 // indirect + github.com/kylelemons/godebug v1.1.0 // indirect + github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + golang.org/x/crypto v0.7.0 // indirect + golang.org/x/net v0.8.0 // indirect + golang.org/x/sys v0.6.0 // indirect + golang.org/x/text v0.8.0 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/sdk/ai/azopenai/go.sum b/sdk/ai/azopenai/go.sum new file mode 100644 index 000000000000..10901f2ec9f9 --- /dev/null +++ b/sdk/ai/azopenai/go.sum @@ -0,0 +1,46 @@ +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.6.1 h1:SEy2xmstIphdPwNBUi7uhvjyjhVKISfwjfOJmuy7kg4= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.6.1/go.mod h1:bjGvMhVMb+EEm3VRNQawDMUyMMjo+S5ewNjflkep/0Q= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0 h1:vcYCAze6p19qBW7MhZybIsqD8sMV8js0NyQM8JDnVtg= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0/go.mod h1:OQeznEEkTZ9OrhHJoDD8ZDq51FHgXjqtP9z6bEwBq9U= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0 h1:sXr+ck84g/ZlZUOZiNELInmMgOsuGwdjjVkEIde0OtY= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0/go.mod h1:okt5dMMTOFjX/aovMlrjvvXoPMBVSPzk9185BT0+eZM= +github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0 h1:OBhqkivkhkMqLPymWEppkm7vgPQY2XsHoEkaMQ0AdZY= +github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0/go.mod h1:kgDmCTgBzIEPFElEF+FK0SdjAor06dRq2Go927dnQ6o= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI= +github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ= +github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= +github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= +github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8= +github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU= +github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A= +golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= +golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ= +golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= +golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68= +golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/sdk/ai/azopenai/main_test.go b/sdk/ai/azopenai/main_test.go new file mode 100644 index 000000000000..2890ad613e61 --- /dev/null +++ b/sdk/ai/azopenai/main_test.go @@ -0,0 +1,14 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +package azopenai_test + +import ( + "os" + "testing" +) + +func TestMain(m *testing.M) { + initEnvVars() + os.Exit(m.Run()) +} diff --git a/sdk/ai/azopenai/models.go b/sdk/ai/azopenai/models.go new file mode 100644 index 000000000000..2eb3a62b490d --- /dev/null +++ b/sdk/ai/azopenai/models.go @@ -0,0 +1,707 @@ +//go:build go1.18 +// +build go1.18 + +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// Code generated by Microsoft (R) AutoRest Code Generator. DO NOT EDIT. +// Changes may cause incorrect behavior and will be lost if the code is regenerated. + +package azopenai + +// azureCoreFoundationsError - The error object. +type azureCoreFoundationsError struct { + // REQUIRED; One of a server-defined set of error codes. + Code *string + + // REQUIRED; A human-readable representation of the error. + Message *string + + // An array of details about specific errors that led to this reported error. + Details []azureCoreFoundationsError + + // An object containing more specific information than the current object about the error. + Innererror *azureCoreFoundationsErrorInnererror + + // The target of the error. + Target *string +} + +// azureCoreFoundationsErrorInnererror - An object containing more specific information than the current object about the +// error. +type azureCoreFoundationsErrorInnererror struct { + // One of a server-defined set of error codes. + Code *string + + // Inner error. + Innererror *azureCoreFoundationsInnerErrorInnererror +} + +// azureCoreFoundationsErrorResponse - A response containing error details. +type azureCoreFoundationsErrorResponse struct { + // REQUIRED; The error object. + Error *azureCoreFoundationsErrorResponseError +} + +// azureCoreFoundationsErrorResponseError - The error object. +type azureCoreFoundationsErrorResponseError struct { + // REQUIRED; One of a server-defined set of error codes. + Code *string + + // REQUIRED; A human-readable representation of the error. + Message *string + + // An array of details about specific errors that led to this reported error. + Details []azureCoreFoundationsError + + // An object containing more specific information than the current object about the error. + Innererror *azureCoreFoundationsErrorInnererror + + // The target of the error. + Target *string +} + +// azureCoreFoundationsInnerError - 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. +type azureCoreFoundationsInnerError struct { + // One of a server-defined set of error codes. + Code *string + + // Inner error. + Innererror *azureCoreFoundationsInnerErrorInnererror +} + +// azureCoreFoundationsInnerErrorInnererror - Inner error. +type azureCoreFoundationsInnerErrorInnererror struct { + // One of a server-defined set of error codes. + Code *string + + // Inner error. + Innererror *azureCoreFoundationsInnerErrorInnererror +} + +// batchImageGenerationOperationResponse - A polling status update or final response payload for an image operation. +type batchImageGenerationOperationResponse struct { + // REQUIRED; A timestamp when this job or item was created (in unix epochs). + Created *int64 + + // REQUIRED; The ID of the operation. + ID *string + + // REQUIRED; The status of the operation + Status *azureOpenAIOperationState + + // The error if the operation failed. + Error *azureCoreFoundationsError + + // A timestamp when this operation and its associated images expire and will be deleted (in unix epochs). + Expires *int64 + + // The result of the operation if the operation succeeded. + Result *ImageGenerations +} + +// ChatChoice - The representation of a single prompt completion as part of an overall chat completions request. Generally, +// n choices are generated per provided prompt with a default value of 1. Token limits and +// other settings may limit the number of choices generated. +type ChatChoice struct { + // REQUIRED; The reason that this chat completions choice completed its generated. + FinishReason *CompletionsFinishReason + + // REQUIRED; The ordered index associated with this chat completions choice. + Index *int32 + + // Information about the content filtering category (hate, sexual, violence, selfharm), if it has been detected, as well as + // the severity level (verylow, low, medium, high-scale that determines the + // intensity and risk level of harmful content) and if it has been filtered or not. + ContentFilterResults *ChatChoiceContentFilterResults + + // The delta message content for a streaming response. + Delta *ChatChoiceDelta + + // The chat message for a given chat completions prompt. + Message *ChatChoiceMessage +} + +// ChatChoiceContentFilterResults - Information about the content filtering category (hate, sexual, violence, selfharm), if +// it has been detected, as well as the severity level (verylow, low, medium, high-scale that determines the +// intensity and risk level of harmful content) and if it has been filtered or not. +type ChatChoiceContentFilterResults struct { + // REQUIRED; Describes language attacks or uses that include pejorative or discriminatory language with reference to a person + // or identity group on the basis of certain differentiating attributes of these groups + // including but not limited to race, ethnicity, nationality, gender identity and expression, sexual orientation, religion, + // immigration status, ability status, personal appearance, and body size. + Hate *ContentFilterResultsHate + + // REQUIRED; Describes language related to physical actions intended to purposely hurt, injure, or damage one’s body, or kill + // oneself. + SelfHarm *ContentFilterResultsSelfHarm + + // REQUIRED; Describes language related to anatomical organs and genitals, romantic relationships, acts portrayed in erotic + // or affectionate terms, physical sexual acts, including those portrayed as an assault or a + // forced sexual violent act against one’s will, prostitution, pornography, and abuse. + Sexual *ContentFilterResultsSexual + + // REQUIRED; Describes language related to physical actions intended to hurt, injure, damage, or kill someone or something; + // describes weapons, etc. + Violence *ContentFilterResultsViolence +} + +// ChatChoiceDelta - The delta message content for a streaming response. +type ChatChoiceDelta struct { + // REQUIRED; The role associated with this message payload. + Role *ChatRole + + // The text associated with this message payload. + Content *string + + // The name and arguments of a function that should be called, as generated by the model. + FunctionCall *ChatMessageFunctionCall + + // The name of the author of this message. name is required if role is function, and it should be the name of the function + // whose response is in the content. May contain a-z, A-Z, 0-9, and underscores, + // with a maximum length of 64 characters. + Name *string +} + +// ChatChoiceMessage - The chat message for a given chat completions prompt. +type ChatChoiceMessage struct { + // REQUIRED; The role associated with this message payload. + Role *ChatRole + + // The text associated with this message payload. + Content *string + + // The name and arguments of a function that should be called, as generated by the model. + FunctionCall *ChatMessageFunctionCall + + // The name of the author of this message. name is required if role is function, and it should be the name of the function + // whose response is in the content. May contain a-z, A-Z, 0-9, and underscores, + // with a maximum length of 64 characters. + Name *string +} + +// ChatCompletions - Representation of the response data from a chat completions request. Completions support a wide variety +// of tasks and generate text that continues from or "completes" provided prompt data. +type ChatCompletions struct { + // REQUIRED; The collection of completions choices associated with this completions response. Generally, n choices are generated + // per provided prompt with a default value of 1. Token limits and other settings may + // limit the number of choices generated. + Choices []ChatChoice + + // REQUIRED; The first timestamp associated with generation activity for this completions response, represented as seconds + // since the beginning of the Unix epoch of 00:00 on 1 Jan 1970. + Created *int32 + + // REQUIRED; A unique identifier associated with this chat completions response. + ID *string + + // REQUIRED; Usage information for tokens processed and generated as part of this completions operation. + Usage *CompletionsUsage + + // Content filtering results for zero or more prompts in the request. In a streaming request, results for different prompts + // may arrive at different times or in different orders. + PromptAnnotations []PromptFilterResult +} + +// ChatCompletionsOptions - The configuration information for a chat completions request. Completions support a wide variety +// of tasks and generate text that continues from or "completes" provided prompt data. +type ChatCompletionsOptions struct { + // REQUIRED; The collection of context messages associated with this chat completions request. Typical usage begins with a + // chat message for the System role that provides instructions for the behavior of the + // assistant, followed by alternating messages between the User and Assistant roles. + Messages []ChatMessage + + // A value that influences the probability of generated tokens appearing based on their cumulative frequency in generated + // text. Positive values will make tokens less likely to appear as their frequency + // increases and decrease the likelihood of the model repeating the same statements verbatim. + FrequencyPenalty *float32 + + // Controls how the model responds to function calls. "none" means the model does not call a function, and responds to the + // end-user. "auto" means the model can pick between an end-user or calling a + // function. Specifying a particular function via {"name": "my_function"} forces the model to call that function. "none" is + // the default when no functions are present. "auto" is the default if functions + // are present. + FunctionCall *ChatCompletionsOptionsFunctionCall + + // A list of functions the model may generate JSON inputs for. + Functions []FunctionDefinition + + // A map between GPT token IDs and bias scores that influences the probability of specific tokens appearing in a completions + // response. Token IDs are computed via external tokenizer tools, while bias + // scores reside in the range of -100 to 100 with minimum and maximum values corresponding to a full ban or exclusive selection + // of a token, respectively. The exact behavior of a given bias score varies + // by model. + LogitBias map[string]*int32 + + // The maximum number of tokens to generate. + MaxTokens *int32 + + // REQUIRED: DeploymentID specifies the name of the deployment (for Azure OpenAI) or model (for OpenAI) to use for this request. + DeploymentID string + + // The number of chat completions choices that should be generated for a chat completions response. Because this setting can + // generate many completions, it may quickly consume your token quota. Use + // carefully and ensure reasonable settings for max_tokens and stop. + N *int32 + + // A value that influences the probability of generated tokens appearing based on their existing presence in generated text. + // Positive values will make tokens less likely to appear when they already exist + // and increase the model's likelihood to output new topics. + PresencePenalty *float32 + + // A collection of textual sequences that will end completions generation. + Stop []string + + // The sampling temperature to use that controls the apparent creativity of generated completions. Higher values will make + // output more random while lower values will make results more focused and + // deterministic. It is not recommended to modify temperature and top_p for the same completions request as the interaction + // of these two settings is difficult to predict. + Temperature *float32 + + // An alternative to sampling with temperature called nucleus sampling. This value causes the model to consider the results + // of tokens with the provided probability mass. As an example, a value of 0.15 + // will cause only the tokens comprising the top 15% of probability mass to be considered. It is not recommended to modify + // temperature and top_p for the same completions request as the interaction of + // these two settings is difficult to predict. + TopP *float32 + + // An identifier for the caller or end user of the operation. This may be used for tracking or rate-limiting purposes. + User *string +} + +// ChatMessage - A single, role-attributed message within a chat completion interaction. +type ChatMessage struct { + // REQUIRED; The role associated with this message payload. + Role *ChatRole + + // The text associated with this message payload. + Content *string + + // The name and arguments of a function that should be called, as generated by the model. + FunctionCall *ChatMessageFunctionCall + + // The name of the author of this message. name is required if role is function, and it should be the name of the function + // whose response is in the content. May contain a-z, A-Z, 0-9, and underscores, + // with a maximum length of 64 characters. + Name *string +} + +// ChatMessageFunctionCall - The name and arguments of a function that should be called, as generated by the model. +type ChatMessageFunctionCall struct { + // REQUIRED; The arguments to call the function with, as generated by the model in JSON format. Note that the model does not + // always generate valid JSON, and may hallucinate parameters not defined by your function + // schema. Validate the arguments in your code before calling your function. + Arguments *string + + // REQUIRED; The name of the function to call. + Name *string +} + +// Choice - The representation of a single prompt completion as part of an overall completions request. Generally, n choices +// are generated per provided prompt with a default value of 1. Token limits and other +// settings may limit the number of choices generated. +type Choice struct { + // REQUIRED; Reason for finishing + FinishReason *CompletionsFinishReason + + // REQUIRED; The ordered index associated with this completions choice. + Index *int32 + + // REQUIRED; The log probabilities model for tokens associated with this completions choice. + LogProbs *ChoiceLogProbs + + // REQUIRED; The generated text for a given completions prompt. + Text *string + + // Information about the content filtering category (hate, sexual, violence, selfharm), if it has been detected, as well as + // the severity level (verylow, low, medium, high-scale that determines the + // intensity and risk level of harmful content) and if it has been filtered or not. + ContentFilterResults *ChoiceContentFilterResults +} + +// ChoiceContentFilterResults - Information about the content filtering category (hate, sexual, violence, selfharm), if it +// has been detected, as well as the severity level (verylow, low, medium, high-scale that determines the +// intensity and risk level of harmful content) and if it has been filtered or not. +type ChoiceContentFilterResults struct { + // REQUIRED; Describes language attacks or uses that include pejorative or discriminatory language with reference to a person + // or identity group on the basis of certain differentiating attributes of these groups + // including but not limited to race, ethnicity, nationality, gender identity and expression, sexual orientation, religion, + // immigration status, ability status, personal appearance, and body size. + Hate *ContentFilterResultsHate + + // REQUIRED; Describes language related to physical actions intended to purposely hurt, injure, or damage one’s body, or kill + // oneself. + SelfHarm *ContentFilterResultsSelfHarm + + // REQUIRED; Describes language related to anatomical organs and genitals, romantic relationships, acts portrayed in erotic + // or affectionate terms, physical sexual acts, including those portrayed as an assault or a + // forced sexual violent act against one’s will, prostitution, pornography, and abuse. + Sexual *ContentFilterResultsSexual + + // REQUIRED; Describes language related to physical actions intended to hurt, injure, damage, or kill someone or something; + // describes weapons, etc. + Violence *ContentFilterResultsViolence +} + +// ChoiceLogProbs - The log probabilities model for tokens associated with this completions choice. +type ChoiceLogProbs struct { + // REQUIRED; The text offsets associated with tokens in this completions data. + TextOffset []int32 + + // REQUIRED; A collection of log probability values for the tokens in this completions data. + TokenLogProbs []float32 + + // REQUIRED; The textual forms of tokens evaluated in this probability model. + Tokens []string + + // REQUIRED; A mapping of tokens to maximum log probability values in this completions data. + TopLogProbs []any +} + +// Completions - Representation of the response data from a completions request. Completions support a wide variety of tasks +// and generate text that continues from or "completes" provided prompt data. +type Completions struct { + // REQUIRED; The collection of completions choices associated with this completions response. Generally, n choices are generated + // per provided prompt with a default value of 1. Token limits and other settings may + // limit the number of choices generated. + Choices []Choice + + // REQUIRED; The first timestamp associated with generation activity for this completions response, represented as seconds + // since the beginning of the Unix epoch of 00:00 on 1 Jan 1970. + Created *int32 + + // REQUIRED; A unique identifier associated with this completions response. + ID *string + + // REQUIRED; Usage information for tokens processed and generated as part of this completions operation. + Usage *CompletionsUsage + + // Content filtering results for zero or more prompts in the request. In a streaming request, results for different prompts + // may arrive at different times or in different orders. + PromptAnnotations []PromptFilterResult +} + +// CompletionsLogProbabilityModel - Representation of a log probabilities model for a completions generation. +type CompletionsLogProbabilityModel struct { + // REQUIRED; The text offsets associated with tokens in this completions data. + TextOffset []int32 + + // REQUIRED; A collection of log probability values for the tokens in this completions data. + TokenLogProbs []float32 + + // REQUIRED; The textual forms of tokens evaluated in this probability model. + Tokens []string + + // REQUIRED; A mapping of tokens to maximum log probability values in this completions data. + TopLogProbs []any +} + +// CompletionsOptions - The configuration information for a completions request. Completions support a wide variety of tasks +// and generate text that continues from or "completes" provided prompt data. +type CompletionsOptions struct { + // REQUIRED; The prompts to generate completions from. + Prompt []string + + // A value that controls how many completions will be internally generated prior to response formulation. When used together + // with n, bestof controls the number of candidate completions and must be + // greater than n. Because this setting can generate many completions, it may quickly consume your token quota. Use carefully + // and ensure reasonable settings for maxtokens and stop. + BestOf *int32 + + // A value specifying whether completions responses should include input prompts as prefixes to their generated output. + Echo *bool + + // A value that influences the probability of generated tokens appearing based on their cumulative frequency in generated + // text. Positive values will make tokens less likely to appear as their frequency + // increases and decrease the likelihood of the model repeating the same statements verbatim. + FrequencyPenalty *float32 + + // A map between GPT token IDs and bias scores that influences the probability of specific tokens appearing in a completions + // response. Token IDs are computed via external tokenizer tools, while bias + // scores reside in the range of -100 to 100 with minimum and maximum values corresponding to a full ban or exclusive selection + // of a token, respectively. The exact behavior of a given bias score varies + // by model. + LogitBias map[string]*int32 + + // A value that controls the emission of log probabilities for the provided number of most likely tokens within a completions + // response. + LogProbs *int32 + + // The maximum number of tokens to generate. + MaxTokens *int32 + + // REQUIRED: DeploymentID specifies the name of the deployment (for Azure OpenAI) or model (for OpenAI) to use for this request. + DeploymentID string + + // The number of completions choices that should be generated per provided prompt as part of an overall completions response. + // Because this setting can generate many completions, it may quickly consume + // your token quota. Use carefully and ensure reasonable settings for max_tokens and stop. + N *int32 + + // A value that influences the probability of generated tokens appearing based on their existing presence in generated text. + // Positive values will make tokens less likely to appear when they already exist + // and increase the model's likelihood to output new topics. + PresencePenalty *float32 + + // A collection of textual sequences that will end completions generation. + Stop []string + + // The sampling temperature to use that controls the apparent creativity of generated completions. Higher values will make + // output more random while lower values will make results more focused and + // deterministic. It is not recommended to modify temperature and top_p for the same completions request as the interaction + // of these two settings is difficult to predict. + Temperature *float32 + + // An alternative to sampling with temperature called nucleus sampling. This value causes the model to consider the results + // of tokens with the provided probability mass. As an example, a value of 0.15 + // will cause only the tokens comprising the top 15% of probability mass to be considered. It is not recommended to modify + // temperature and top_p for the same completions request as the interaction of + // these two settings is difficult to predict. + TopP *float32 + + // An identifier for the caller or end user of the operation. This may be used for tracking or rate-limiting purposes. + User *string +} + +// CompletionsUsage - Representation of the token counts processed for a completions request. Counts consider all tokens across +// prompts, choices, choice alternates, best_of generations, and other consumers. +type CompletionsUsage struct { + // REQUIRED; The number of tokens generated across all completions emissions. + CompletionTokens *int32 + + // REQUIRED; The number of tokens in the provided prompts for the completions request. + PromptTokens *int32 + + // REQUIRED; The total number of tokens processed for the completions request and response. + TotalTokens *int32 +} + +// ContentFilterResults - Information about the content filtering category, if it has been detected. +type ContentFilterResults struct { + // REQUIRED; Describes language attacks or uses that include pejorative or discriminatory language with reference to a person + // or identity group on the basis of certain differentiating attributes of these groups + // including but not limited to race, ethnicity, nationality, gender identity and expression, sexual orientation, religion, + // immigration status, ability status, personal appearance, and body size. + Hate *ContentFilterResultsHate + + // REQUIRED; Describes language related to physical actions intended to purposely hurt, injure, or damage one’s body, or kill + // oneself. + SelfHarm *ContentFilterResultsSelfHarm + + // REQUIRED; Describes language related to anatomical organs and genitals, romantic relationships, acts portrayed in erotic + // or affectionate terms, physical sexual acts, including those portrayed as an assault or a + // forced sexual violent act against one’s will, prostitution, pornography, and abuse. + Sexual *ContentFilterResultsSexual + + // REQUIRED; Describes language related to physical actions intended to hurt, injure, damage, or kill someone or something; + // describes weapons, etc. + Violence *ContentFilterResultsViolence +} + +// ContentFilterResultsHate - Describes language attacks or uses that include pejorative or discriminatory language with reference +// to a person or identity group on the basis of certain differentiating attributes of these groups +// including but not limited to race, ethnicity, nationality, gender identity and expression, sexual orientation, religion, +// immigration status, ability status, personal appearance, and body size. +type ContentFilterResultsHate struct { + // REQUIRED; A value indicating whether or not the content has been filtered. + Filtered *bool + + // REQUIRED; Ratings for the intensity and risk level of filtered content. + Severity *ContentFilterSeverity +} + +// ContentFilterResultsSelfHarm - Describes language related to physical actions intended to purposely hurt, injure, or damage +// one’s body, or kill oneself. +type ContentFilterResultsSelfHarm struct { + // REQUIRED; A value indicating whether or not the content has been filtered. + Filtered *bool + + // REQUIRED; Ratings for the intensity and risk level of filtered content. + Severity *ContentFilterSeverity +} + +// ContentFilterResultsSexual - Describes language related to anatomical organs and genitals, romantic relationships, acts +// portrayed in erotic or affectionate terms, physical sexual acts, including those portrayed as an assault or a +// forced sexual violent act against one’s will, prostitution, pornography, and abuse. +type ContentFilterResultsSexual struct { + // REQUIRED; A value indicating whether or not the content has been filtered. + Filtered *bool + + // REQUIRED; Ratings for the intensity and risk level of filtered content. + Severity *ContentFilterSeverity +} + +// ContentFilterResultsViolence - Describes language related to physical actions intended to hurt, injure, damage, or kill +// someone or something; describes weapons, etc. +type ContentFilterResultsViolence struct { + // REQUIRED; A value indicating whether or not the content has been filtered. + Filtered *bool + + // REQUIRED; Ratings for the intensity and risk level of filtered content. + Severity *ContentFilterSeverity +} + +// Deployment - A specific deployment +type Deployment struct { + // READ-ONLY; Specifies either the model deployment name (when using Azure OpenAI) or model name (when using non-Azure OpenAI) + // to use for this request. + DeploymentID *string +} + +// EmbeddingItem - Representation of a single embeddings relatedness comparison. +type EmbeddingItem struct { + // REQUIRED; List of embeddings value for the input prompt. These represent a measurement of the vector-based relatedness + // of the provided input. + Embedding []float32 + + // REQUIRED; Index of the prompt to which the EmbeddingItem corresponds. + Index *int32 +} + +// Embeddings - Representation of the response data from an embeddings request. Embeddings measure the relatedness of text +// strings and are commonly used for search, clustering, recommendations, and other similar +// scenarios. +type Embeddings struct { + // REQUIRED; Embedding values for the prompts submitted in the request. + Data []EmbeddingItem + + // REQUIRED; Usage counts for tokens input using the embeddings API. + Usage *EmbeddingsUsage +} + +// EmbeddingsOptions - The configuration information for an embeddings request. Embeddings measure the relatedness of text +// strings and are commonly used for search, clustering, recommendations, and other similar scenarios. +type EmbeddingsOptions struct { + // REQUIRED; Input texts to get embeddings for, encoded as a an array of strings. Each input must not exceed 2048 tokens in + // length. + // Unless you are embedding code, we suggest replacing newlines (\n) in your input with a single space, as we have observed + // inferior results when newlines are present. + Input []string + + // REQUIRED: DeploymentID specifies the name of the deployment (for Azure OpenAI) or model (for OpenAI) to use for this request. + DeploymentID string + + // An identifier for the caller or end user of the operation. This may be used for tracking or rate-limiting purposes. + User *string +} + +// EmbeddingsUsage - Usage counts for tokens input using the embeddings API. +type EmbeddingsUsage struct { + // REQUIRED; Number of tokens sent in the original request. + PromptTokens *int32 + + // REQUIRED; Total number of tokens transacted in this request/response. + TotalTokens *int32 +} + +// EmbeddingsUsageAutoGenerated - Measurement of the amount of tokens used in this request and response. +type EmbeddingsUsageAutoGenerated struct { + // REQUIRED; Number of tokens sent in the original request. + PromptTokens *int32 + + // REQUIRED; Total number of tokens transacted in this request/response. + TotalTokens *int32 +} + +// FunctionCall - The name and arguments of a function that should be called, as generated by the model. +type FunctionCall struct { + // REQUIRED; The arguments to call the function with, as generated by the model in JSON format. Note that the model does not + // always generate valid JSON, and may hallucinate parameters not defined by your function + // schema. Validate the arguments in your code before calling your function. + Arguments *string + + // REQUIRED; The name of the function to call. + Name *string +} + +// FunctionDefinition - The definition of a caller-specified function that chat completions may invoke in response to matching +// user input. +type FunctionDefinition struct { + // REQUIRED; The name of the function to be called. + Name *string + + // A description of what the function does. The model will use this description when selecting the function and interpreting + // its parameters. + Description *string + + // The parameters the functions accepts, described as a JSON Schema object. + Parameters any +} + +// FunctionName - A structure that specifies the exact name of a specific, request-provided function to use when processing +// a chat completions operation. +type FunctionName struct { + // REQUIRED; The name of the function to call. + Name *string +} + +// ImageGenerationOptions - Represents the request data used to generate images. +type ImageGenerationOptions struct { + // REQUIRED; A description of the desired images. + Prompt *string + + // The number of images to generate (defaults to 1). + N *int32 + + // The format in which image generation response items should be presented. Azure OpenAI only supports URL response items. + ResponseFormat *ImageGenerationResponseFormat + + // The desired size of the generated images. Must be one of 256x256, 512x512, or 1024x1024 (defaults to 1024x1024). + Size *ImageSize + + // A unique identifier representing your end-user, which can help to monitor and detect abuse. + User *string +} + +// ImageGenerations - The result of the operation if the operation succeeded. +type ImageGenerations struct { + // REQUIRED; A timestamp when this job or item was created (in unix epochs). + Created *int64 + + // REQUIRED; The images generated by the operator. + Data []ImageGenerationsDataItem +} + +// ImageLocation - An image response item that provides a URL from which an image may be accessed. +type ImageLocation struct { + // REQUIRED; The URL that provides temporary access to download the generated image. + URL *string +} + +// ImagePayload - An image response item that directly represents the image data as a base64-encoded string. +type ImagePayload struct { + // REQUIRED; The complete data for an image represented as a base64-encoded string. + B64JSON *string +} + +// PromptFilterResult - Content filtering results for a single prompt in the request. +type PromptFilterResult struct { + // REQUIRED; The index of this prompt in the set of prompt results + PromptIndex *int32 + + // Content filtering results for this prompt + ContentFilterResults *PromptFilterResultContentFilterResults +} + +// PromptFilterResultContentFilterResults - Content filtering results for this prompt +type PromptFilterResultContentFilterResults struct { + // REQUIRED; Describes language attacks or uses that include pejorative or discriminatory language with reference to a person + // or identity group on the basis of certain differentiating attributes of these groups + // including but not limited to race, ethnicity, nationality, gender identity and expression, sexual orientation, religion, + // immigration status, ability status, personal appearance, and body size. + Hate *ContentFilterResultsHate + + // REQUIRED; Describes language related to physical actions intended to purposely hurt, injure, or damage one’s body, or kill + // oneself. + SelfHarm *ContentFilterResultsSelfHarm + + // REQUIRED; Describes language related to anatomical organs and genitals, romantic relationships, acts portrayed in erotic + // or affectionate terms, physical sexual acts, including those portrayed as an assault or a + // forced sexual violent act against one’s will, prostitution, pornography, and abuse. + Sexual *ContentFilterResultsSexual + + // REQUIRED; Describes language related to physical actions intended to hurt, injure, damage, or kill someone or something; + // describes weapons, etc. + Violence *ContentFilterResultsViolence +} diff --git a/sdk/ai/azopenai/models_serde.go b/sdk/ai/azopenai/models_serde.go new file mode 100644 index 000000000000..7af3419c5f09 --- /dev/null +++ b/sdk/ai/azopenai/models_serde.go @@ -0,0 +1,1551 @@ +//go:build go1.18 +// +build go1.18 + +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// Code generated by Microsoft (R) AutoRest Code Generator. DO NOT EDIT. +// Changes may cause incorrect behavior and will be lost if the code is regenerated. + +package azopenai + +import ( + "encoding/json" + "fmt" + "reflect" + + "github.com/Azure/azure-sdk-for-go/sdk/azcore" +) + +// MarshalJSON implements the json.Marshaller interface for type azureCoreFoundationsError. +func (a azureCoreFoundationsError) MarshalJSON() ([]byte, error) { + objectMap := make(map[string]any) + populate(objectMap, "code", a.Code) + populate(objectMap, "details", a.Details) + populate(objectMap, "innererror", a.Innererror) + populate(objectMap, "message", a.Message) + populate(objectMap, "target", a.Target) + return json.Marshal(objectMap) +} + +// UnmarshalJSON implements the json.Unmarshaller interface for type azureCoreFoundationsError. +func (a *azureCoreFoundationsError) UnmarshalJSON(data []byte) error { + var rawMsg map[string]json.RawMessage + if err := json.Unmarshal(data, &rawMsg); err != nil { + return fmt.Errorf("unmarshalling type %T: %v", a, err) + } + for key, val := range rawMsg { + var err error + switch key { + case "code": + err = unpopulate(val, "Code", &a.Code) + delete(rawMsg, key) + case "details": + err = unpopulate(val, "Details", &a.Details) + delete(rawMsg, key) + case "innererror": + err = unpopulate(val, "Innererror", &a.Innererror) + delete(rawMsg, key) + case "message": + err = unpopulate(val, "Message", &a.Message) + delete(rawMsg, key) + case "target": + err = unpopulate(val, "Target", &a.Target) + delete(rawMsg, key) + } + if err != nil { + return fmt.Errorf("unmarshalling type %T: %v", a, err) + } + } + return nil +} + +// MarshalJSON implements the json.Marshaller interface for type azureCoreFoundationsErrorInnererror. +func (a azureCoreFoundationsErrorInnererror) MarshalJSON() ([]byte, error) { + objectMap := make(map[string]any) + populate(objectMap, "code", a.Code) + populate(objectMap, "innererror", a.Innererror) + return json.Marshal(objectMap) +} + +// UnmarshalJSON implements the json.Unmarshaller interface for type azureCoreFoundationsErrorInnererror. +func (a *azureCoreFoundationsErrorInnererror) UnmarshalJSON(data []byte) error { + var rawMsg map[string]json.RawMessage + if err := json.Unmarshal(data, &rawMsg); err != nil { + return fmt.Errorf("unmarshalling type %T: %v", a, err) + } + for key, val := range rawMsg { + var err error + switch key { + case "code": + err = unpopulate(val, "Code", &a.Code) + delete(rawMsg, key) + case "innererror": + err = unpopulate(val, "Innererror", &a.Innererror) + delete(rawMsg, key) + } + if err != nil { + return fmt.Errorf("unmarshalling type %T: %v", a, err) + } + } + return nil +} + +// MarshalJSON implements the json.Marshaller interface for type azureCoreFoundationsErrorResponse. +func (a azureCoreFoundationsErrorResponse) MarshalJSON() ([]byte, error) { + objectMap := make(map[string]any) + populate(objectMap, "error", a.Error) + return json.Marshal(objectMap) +} + +// UnmarshalJSON implements the json.Unmarshaller interface for type azureCoreFoundationsErrorResponse. +func (a *azureCoreFoundationsErrorResponse) UnmarshalJSON(data []byte) error { + var rawMsg map[string]json.RawMessage + if err := json.Unmarshal(data, &rawMsg); err != nil { + return fmt.Errorf("unmarshalling type %T: %v", a, err) + } + for key, val := range rawMsg { + var err error + switch key { + case "error": + err = unpopulate(val, "Error", &a.Error) + delete(rawMsg, key) + } + if err != nil { + return fmt.Errorf("unmarshalling type %T: %v", a, err) + } + } + return nil +} + +// MarshalJSON implements the json.Marshaller interface for type azureCoreFoundationsErrorResponseError. +func (a azureCoreFoundationsErrorResponseError) MarshalJSON() ([]byte, error) { + objectMap := make(map[string]any) + populate(objectMap, "code", a.Code) + populate(objectMap, "details", a.Details) + populate(objectMap, "innererror", a.Innererror) + populate(objectMap, "message", a.Message) + populate(objectMap, "target", a.Target) + return json.Marshal(objectMap) +} + +// UnmarshalJSON implements the json.Unmarshaller interface for type azureCoreFoundationsErrorResponseError. +func (a *azureCoreFoundationsErrorResponseError) UnmarshalJSON(data []byte) error { + var rawMsg map[string]json.RawMessage + if err := json.Unmarshal(data, &rawMsg); err != nil { + return fmt.Errorf("unmarshalling type %T: %v", a, err) + } + for key, val := range rawMsg { + var err error + switch key { + case "code": + err = unpopulate(val, "Code", &a.Code) + delete(rawMsg, key) + case "details": + err = unpopulate(val, "Details", &a.Details) + delete(rawMsg, key) + case "innererror": + err = unpopulate(val, "Innererror", &a.Innererror) + delete(rawMsg, key) + case "message": + err = unpopulate(val, "Message", &a.Message) + delete(rawMsg, key) + case "target": + err = unpopulate(val, "Target", &a.Target) + delete(rawMsg, key) + } + if err != nil { + return fmt.Errorf("unmarshalling type %T: %v", a, err) + } + } + return nil +} + +// MarshalJSON implements the json.Marshaller interface for type azureCoreFoundationsInnerError. +func (a azureCoreFoundationsInnerError) MarshalJSON() ([]byte, error) { + objectMap := make(map[string]any) + populate(objectMap, "code", a.Code) + populate(objectMap, "innererror", a.Innererror) + return json.Marshal(objectMap) +} + +// UnmarshalJSON implements the json.Unmarshaller interface for type azureCoreFoundationsInnerError. +func (a *azureCoreFoundationsInnerError) UnmarshalJSON(data []byte) error { + var rawMsg map[string]json.RawMessage + if err := json.Unmarshal(data, &rawMsg); err != nil { + return fmt.Errorf("unmarshalling type %T: %v", a, err) + } + for key, val := range rawMsg { + var err error + switch key { + case "code": + err = unpopulate(val, "Code", &a.Code) + delete(rawMsg, key) + case "innererror": + err = unpopulate(val, "Innererror", &a.Innererror) + delete(rawMsg, key) + } + if err != nil { + return fmt.Errorf("unmarshalling type %T: %v", a, err) + } + } + return nil +} + +// MarshalJSON implements the json.Marshaller interface for type azureCoreFoundationsInnerErrorInnererror. +func (a azureCoreFoundationsInnerErrorInnererror) MarshalJSON() ([]byte, error) { + objectMap := make(map[string]any) + populate(objectMap, "code", a.Code) + populate(objectMap, "innererror", a.Innererror) + return json.Marshal(objectMap) +} + +// UnmarshalJSON implements the json.Unmarshaller interface for type azureCoreFoundationsInnerErrorInnererror. +func (a *azureCoreFoundationsInnerErrorInnererror) UnmarshalJSON(data []byte) error { + var rawMsg map[string]json.RawMessage + if err := json.Unmarshal(data, &rawMsg); err != nil { + return fmt.Errorf("unmarshalling type %T: %v", a, err) + } + for key, val := range rawMsg { + var err error + switch key { + case "code": + err = unpopulate(val, "Code", &a.Code) + delete(rawMsg, key) + case "innererror": + err = unpopulate(val, "Innererror", &a.Innererror) + delete(rawMsg, key) + } + if err != nil { + return fmt.Errorf("unmarshalling type %T: %v", a, err) + } + } + return nil +} + +// MarshalJSON implements the json.Marshaller interface for type batchImageGenerationOperationResponse. +func (b batchImageGenerationOperationResponse) MarshalJSON() ([]byte, error) { + objectMap := make(map[string]any) + populate(objectMap, "created", b.Created) + populate(objectMap, "error", b.Error) + populate(objectMap, "expires", b.Expires) + populate(objectMap, "id", b.ID) + populate(objectMap, "result", b.Result) + populate(objectMap, "status", b.Status) + return json.Marshal(objectMap) +} + +// UnmarshalJSON implements the json.Unmarshaller interface for type batchImageGenerationOperationResponse. +func (b *batchImageGenerationOperationResponse) UnmarshalJSON(data []byte) error { + var rawMsg map[string]json.RawMessage + if err := json.Unmarshal(data, &rawMsg); err != nil { + return fmt.Errorf("unmarshalling type %T: %v", b, err) + } + for key, val := range rawMsg { + var err error + switch key { + case "created": + err = unpopulate(val, "Created", &b.Created) + delete(rawMsg, key) + case "error": + err = unpopulate(val, "Error", &b.Error) + delete(rawMsg, key) + case "expires": + err = unpopulate(val, "Expires", &b.Expires) + delete(rawMsg, key) + case "id": + err = unpopulate(val, "ID", &b.ID) + delete(rawMsg, key) + case "result": + err = unpopulate(val, "Result", &b.Result) + delete(rawMsg, key) + case "status": + err = unpopulate(val, "Status", &b.Status) + delete(rawMsg, key) + } + if err != nil { + return fmt.Errorf("unmarshalling type %T: %v", b, err) + } + } + return nil +} + +// MarshalJSON implements the json.Marshaller interface for type ChatChoice. +func (c ChatChoice) MarshalJSON() ([]byte, error) { + objectMap := make(map[string]any) + populate(objectMap, "content_filter_results", c.ContentFilterResults) + populate(objectMap, "delta", c.Delta) + populate(objectMap, "finish_reason", c.FinishReason) + populate(objectMap, "index", c.Index) + populate(objectMap, "message", c.Message) + return json.Marshal(objectMap) +} + +// UnmarshalJSON implements the json.Unmarshaller interface for type ChatChoice. +func (c *ChatChoice) UnmarshalJSON(data []byte) error { + var rawMsg map[string]json.RawMessage + if err := json.Unmarshal(data, &rawMsg); err != nil { + return fmt.Errorf("unmarshalling type %T: %v", c, err) + } + for key, val := range rawMsg { + var err error + switch key { + case "content_filter_results": + err = unpopulate(val, "ContentFilterResults", &c.ContentFilterResults) + delete(rawMsg, key) + case "delta": + err = unpopulate(val, "Delta", &c.Delta) + delete(rawMsg, key) + case "finish_reason": + err = unpopulate(val, "FinishReason", &c.FinishReason) + delete(rawMsg, key) + case "index": + err = unpopulate(val, "Index", &c.Index) + delete(rawMsg, key) + case "message": + err = unpopulate(val, "Message", &c.Message) + delete(rawMsg, key) + } + if err != nil { + return fmt.Errorf("unmarshalling type %T: %v", c, err) + } + } + return nil +} + +// MarshalJSON implements the json.Marshaller interface for type ChatChoiceContentFilterResults. +func (c ChatChoiceContentFilterResults) MarshalJSON() ([]byte, error) { + objectMap := make(map[string]any) + populate(objectMap, "hate", c.Hate) + populate(objectMap, "self_harm", c.SelfHarm) + populate(objectMap, "sexual", c.Sexual) + populate(objectMap, "violence", c.Violence) + return json.Marshal(objectMap) +} + +// UnmarshalJSON implements the json.Unmarshaller interface for type ChatChoiceContentFilterResults. +func (c *ChatChoiceContentFilterResults) UnmarshalJSON(data []byte) error { + var rawMsg map[string]json.RawMessage + if err := json.Unmarshal(data, &rawMsg); err != nil { + return fmt.Errorf("unmarshalling type %T: %v", c, err) + } + for key, val := range rawMsg { + var err error + switch key { + case "hate": + err = unpopulate(val, "Hate", &c.Hate) + delete(rawMsg, key) + case "self_harm": + err = unpopulate(val, "SelfHarm", &c.SelfHarm) + delete(rawMsg, key) + case "sexual": + err = unpopulate(val, "Sexual", &c.Sexual) + delete(rawMsg, key) + case "violence": + err = unpopulate(val, "Violence", &c.Violence) + delete(rawMsg, key) + } + if err != nil { + return fmt.Errorf("unmarshalling type %T: %v", c, err) + } + } + return nil +} + +// MarshalJSON implements the json.Marshaller interface for type ChatChoiceDelta. +func (c ChatChoiceDelta) MarshalJSON() ([]byte, error) { + objectMap := make(map[string]any) + populate(objectMap, "content", c.Content) + populate(objectMap, "function_call", c.FunctionCall) + populate(objectMap, "name", c.Name) + populate(objectMap, "role", c.Role) + return json.Marshal(objectMap) +} + +// UnmarshalJSON implements the json.Unmarshaller interface for type ChatChoiceDelta. +func (c *ChatChoiceDelta) UnmarshalJSON(data []byte) error { + var rawMsg map[string]json.RawMessage + if err := json.Unmarshal(data, &rawMsg); err != nil { + return fmt.Errorf("unmarshalling type %T: %v", c, err) + } + for key, val := range rawMsg { + var err error + switch key { + case "content": + err = unpopulate(val, "Content", &c.Content) + delete(rawMsg, key) + case "function_call": + err = unpopulate(val, "FunctionCall", &c.FunctionCall) + delete(rawMsg, key) + case "name": + err = unpopulate(val, "Name", &c.Name) + delete(rawMsg, key) + case "role": + err = unpopulate(val, "Role", &c.Role) + delete(rawMsg, key) + } + if err != nil { + return fmt.Errorf("unmarshalling type %T: %v", c, err) + } + } + return nil +} + +// MarshalJSON implements the json.Marshaller interface for type ChatChoiceMessage. +func (c ChatChoiceMessage) MarshalJSON() ([]byte, error) { + objectMap := make(map[string]any) + populate(objectMap, "content", c.Content) + populate(objectMap, "function_call", c.FunctionCall) + populate(objectMap, "name", c.Name) + populate(objectMap, "role", c.Role) + return json.Marshal(objectMap) +} + +// UnmarshalJSON implements the json.Unmarshaller interface for type ChatChoiceMessage. +func (c *ChatChoiceMessage) UnmarshalJSON(data []byte) error { + var rawMsg map[string]json.RawMessage + if err := json.Unmarshal(data, &rawMsg); err != nil { + return fmt.Errorf("unmarshalling type %T: %v", c, err) + } + for key, val := range rawMsg { + var err error + switch key { + case "content": + err = unpopulate(val, "Content", &c.Content) + delete(rawMsg, key) + case "function_call": + err = unpopulate(val, "FunctionCall", &c.FunctionCall) + delete(rawMsg, key) + case "name": + err = unpopulate(val, "Name", &c.Name) + delete(rawMsg, key) + case "role": + err = unpopulate(val, "Role", &c.Role) + delete(rawMsg, key) + } + if err != nil { + return fmt.Errorf("unmarshalling type %T: %v", c, err) + } + } + return nil +} + +// MarshalJSON implements the json.Marshaller interface for type ChatCompletions. +func (c ChatCompletions) MarshalJSON() ([]byte, error) { + objectMap := make(map[string]any) + populate(objectMap, "choices", c.Choices) + populate(objectMap, "created", c.Created) + populate(objectMap, "id", c.ID) + populate(objectMap, "prompt_annotations", c.PromptAnnotations) + populate(objectMap, "usage", c.Usage) + return json.Marshal(objectMap) +} + +// UnmarshalJSON implements the json.Unmarshaller interface for type ChatCompletions. +func (c *ChatCompletions) UnmarshalJSON(data []byte) error { + var rawMsg map[string]json.RawMessage + if err := json.Unmarshal(data, &rawMsg); err != nil { + return fmt.Errorf("unmarshalling type %T: %v", c, err) + } + for key, val := range rawMsg { + var err error + switch key { + case "choices": + err = unpopulate(val, "Choices", &c.Choices) + delete(rawMsg, key) + case "created": + err = unpopulate(val, "Created", &c.Created) + delete(rawMsg, key) + case "id": + err = unpopulate(val, "ID", &c.ID) + delete(rawMsg, key) + case "prompt_annotations": + err = unpopulate(val, "PromptAnnotations", &c.PromptAnnotations) + delete(rawMsg, key) + case "usage": + err = unpopulate(val, "Usage", &c.Usage) + delete(rawMsg, key) + } + if err != nil { + return fmt.Errorf("unmarshalling type %T: %v", c, err) + } + } + return nil +} + +// MarshalJSON implements the json.Marshaller interface for type ChatCompletionsOptions. +func (c ChatCompletionsOptions) MarshalJSON() ([]byte, error) { + objectMap := make(map[string]any) + populate(objectMap, "frequency_penalty", c.FrequencyPenalty) + populate(objectMap, "function_call", c.FunctionCall) + populate(objectMap, "functions", c.Functions) + populate(objectMap, "logit_bias", c.LogitBias) + populate(objectMap, "max_tokens", c.MaxTokens) + populate(objectMap, "messages", c.Messages) + populate(objectMap, "model", &c.DeploymentID) + populate(objectMap, "n", c.N) + populate(objectMap, "presence_penalty", c.PresencePenalty) + populate(objectMap, "stop", c.Stop) + populate(objectMap, "temperature", c.Temperature) + populate(objectMap, "top_p", c.TopP) + populate(objectMap, "user", c.User) + return json.Marshal(objectMap) +} + +// UnmarshalJSON implements the json.Unmarshaller interface for type ChatCompletionsOptions. +func (c *ChatCompletionsOptions) UnmarshalJSON(data []byte) error { + var rawMsg map[string]json.RawMessage + if err := json.Unmarshal(data, &rawMsg); err != nil { + return fmt.Errorf("unmarshalling type %T: %v", c, err) + } + for key, val := range rawMsg { + var err error + switch key { + case "frequency_penalty": + err = unpopulate(val, "FrequencyPenalty", &c.FrequencyPenalty) + delete(rawMsg, key) + case "function_call": + err = unpopulate(val, "FunctionCall", &c.FunctionCall) + delete(rawMsg, key) + case "functions": + err = unpopulate(val, "Functions", &c.Functions) + delete(rawMsg, key) + case "logit_bias": + err = unpopulate(val, "LogitBias", &c.LogitBias) + delete(rawMsg, key) + case "max_tokens": + err = unpopulate(val, "MaxTokens", &c.MaxTokens) + delete(rawMsg, key) + case "messages": + err = unpopulate(val, "Messages", &c.Messages) + delete(rawMsg, key) + case "model": + err = unpopulate(val, "Model", &c.DeploymentID) + delete(rawMsg, key) + case "n": + err = unpopulate(val, "N", &c.N) + delete(rawMsg, key) + case "presence_penalty": + err = unpopulate(val, "PresencePenalty", &c.PresencePenalty) + delete(rawMsg, key) + case "stop": + err = unpopulate(val, "Stop", &c.Stop) + delete(rawMsg, key) + case "temperature": + err = unpopulate(val, "Temperature", &c.Temperature) + delete(rawMsg, key) + case "top_p": + err = unpopulate(val, "TopP", &c.TopP) + delete(rawMsg, key) + case "user": + err = unpopulate(val, "User", &c.User) + delete(rawMsg, key) + } + if err != nil { + return fmt.Errorf("unmarshalling type %T: %v", c, err) + } + } + return nil +} + +// MarshalJSON implements the json.Marshaller interface for type ChatMessage. +func (c ChatMessage) MarshalJSON() ([]byte, error) { + objectMap := make(map[string]any) + populate(objectMap, "content", c.Content) + populate(objectMap, "function_call", c.FunctionCall) + populate(objectMap, "name", c.Name) + populate(objectMap, "role", c.Role) + return json.Marshal(objectMap) +} + +// UnmarshalJSON implements the json.Unmarshaller interface for type ChatMessage. +func (c *ChatMessage) UnmarshalJSON(data []byte) error { + var rawMsg map[string]json.RawMessage + if err := json.Unmarshal(data, &rawMsg); err != nil { + return fmt.Errorf("unmarshalling type %T: %v", c, err) + } + for key, val := range rawMsg { + var err error + switch key { + case "content": + err = unpopulate(val, "Content", &c.Content) + delete(rawMsg, key) + case "function_call": + err = unpopulate(val, "FunctionCall", &c.FunctionCall) + delete(rawMsg, key) + case "name": + err = unpopulate(val, "Name", &c.Name) + delete(rawMsg, key) + case "role": + err = unpopulate(val, "Role", &c.Role) + delete(rawMsg, key) + } + if err != nil { + return fmt.Errorf("unmarshalling type %T: %v", c, err) + } + } + return nil +} + +// MarshalJSON implements the json.Marshaller interface for type ChatMessageFunctionCall. +func (c ChatMessageFunctionCall) MarshalJSON() ([]byte, error) { + objectMap := make(map[string]any) + populate(objectMap, "arguments", c.Arguments) + populate(objectMap, "name", c.Name) + return json.Marshal(objectMap) +} + +// UnmarshalJSON implements the json.Unmarshaller interface for type ChatMessageFunctionCall. +func (c *ChatMessageFunctionCall) UnmarshalJSON(data []byte) error { + var rawMsg map[string]json.RawMessage + if err := json.Unmarshal(data, &rawMsg); err != nil { + return fmt.Errorf("unmarshalling type %T: %v", c, err) + } + for key, val := range rawMsg { + var err error + switch key { + case "arguments": + err = unpopulate(val, "Arguments", &c.Arguments) + delete(rawMsg, key) + case "name": + err = unpopulate(val, "Name", &c.Name) + delete(rawMsg, key) + } + if err != nil { + return fmt.Errorf("unmarshalling type %T: %v", c, err) + } + } + return nil +} + +// MarshalJSON implements the json.Marshaller interface for type Choice. +func (c Choice) MarshalJSON() ([]byte, error) { + objectMap := make(map[string]any) + populate(objectMap, "content_filter_results", c.ContentFilterResults) + populate(objectMap, "finish_reason", c.FinishReason) + populate(objectMap, "index", c.Index) + populate(objectMap, "logprobs", c.LogProbs) + populate(objectMap, "text", c.Text) + return json.Marshal(objectMap) +} + +// UnmarshalJSON implements the json.Unmarshaller interface for type Choice. +func (c *Choice) UnmarshalJSON(data []byte) error { + var rawMsg map[string]json.RawMessage + if err := json.Unmarshal(data, &rawMsg); err != nil { + return fmt.Errorf("unmarshalling type %T: %v", c, err) + } + for key, val := range rawMsg { + var err error + switch key { + case "content_filter_results": + err = unpopulate(val, "ContentFilterResults", &c.ContentFilterResults) + delete(rawMsg, key) + case "finish_reason": + err = unpopulate(val, "FinishReason", &c.FinishReason) + delete(rawMsg, key) + case "index": + err = unpopulate(val, "Index", &c.Index) + delete(rawMsg, key) + case "logprobs": + err = unpopulate(val, "LogProbs", &c.LogProbs) + delete(rawMsg, key) + case "text": + err = unpopulate(val, "Text", &c.Text) + delete(rawMsg, key) + } + if err != nil { + return fmt.Errorf("unmarshalling type %T: %v", c, err) + } + } + return nil +} + +// MarshalJSON implements the json.Marshaller interface for type ChoiceContentFilterResults. +func (c ChoiceContentFilterResults) MarshalJSON() ([]byte, error) { + objectMap := make(map[string]any) + populate(objectMap, "hate", c.Hate) + populate(objectMap, "self_harm", c.SelfHarm) + populate(objectMap, "sexual", c.Sexual) + populate(objectMap, "violence", c.Violence) + return json.Marshal(objectMap) +} + +// UnmarshalJSON implements the json.Unmarshaller interface for type ChoiceContentFilterResults. +func (c *ChoiceContentFilterResults) UnmarshalJSON(data []byte) error { + var rawMsg map[string]json.RawMessage + if err := json.Unmarshal(data, &rawMsg); err != nil { + return fmt.Errorf("unmarshalling type %T: %v", c, err) + } + for key, val := range rawMsg { + var err error + switch key { + case "hate": + err = unpopulate(val, "Hate", &c.Hate) + delete(rawMsg, key) + case "self_harm": + err = unpopulate(val, "SelfHarm", &c.SelfHarm) + delete(rawMsg, key) + case "sexual": + err = unpopulate(val, "Sexual", &c.Sexual) + delete(rawMsg, key) + case "violence": + err = unpopulate(val, "Violence", &c.Violence) + delete(rawMsg, key) + } + if err != nil { + return fmt.Errorf("unmarshalling type %T: %v", c, err) + } + } + return nil +} + +// MarshalJSON implements the json.Marshaller interface for type ChoiceLogProbs. +func (c ChoiceLogProbs) MarshalJSON() ([]byte, error) { + objectMap := make(map[string]any) + populate(objectMap, "text_offset", c.TextOffset) + populate(objectMap, "token_logprobs", c.TokenLogProbs) + populate(objectMap, "tokens", c.Tokens) + populate(objectMap, "top_logprobs", c.TopLogProbs) + return json.Marshal(objectMap) +} + +// UnmarshalJSON implements the json.Unmarshaller interface for type ChoiceLogProbs. +func (c *ChoiceLogProbs) UnmarshalJSON(data []byte) error { + var rawMsg map[string]json.RawMessage + if err := json.Unmarshal(data, &rawMsg); err != nil { + return fmt.Errorf("unmarshalling type %T: %v", c, err) + } + for key, val := range rawMsg { + var err error + switch key { + case "text_offset": + err = unpopulate(val, "TextOffset", &c.TextOffset) + delete(rawMsg, key) + case "token_logprobs": + err = unpopulate(val, "TokenLogProbs", &c.TokenLogProbs) + delete(rawMsg, key) + case "tokens": + err = unpopulate(val, "Tokens", &c.Tokens) + delete(rawMsg, key) + case "top_logprobs": + err = unpopulate(val, "TopLogProbs", &c.TopLogProbs) + delete(rawMsg, key) + } + if err != nil { + return fmt.Errorf("unmarshalling type %T: %v", c, err) + } + } + return nil +} + +// MarshalJSON implements the json.Marshaller interface for type Completions. +func (c Completions) MarshalJSON() ([]byte, error) { + objectMap := make(map[string]any) + populate(objectMap, "choices", c.Choices) + populate(objectMap, "created", c.Created) + populate(objectMap, "id", c.ID) + populate(objectMap, "prompt_annotations", c.PromptAnnotations) + populate(objectMap, "usage", c.Usage) + return json.Marshal(objectMap) +} + +// UnmarshalJSON implements the json.Unmarshaller interface for type Completions. +func (c *Completions) UnmarshalJSON(data []byte) error { + var rawMsg map[string]json.RawMessage + if err := json.Unmarshal(data, &rawMsg); err != nil { + return fmt.Errorf("unmarshalling type %T: %v", c, err) + } + for key, val := range rawMsg { + var err error + switch key { + case "choices": + err = unpopulate(val, "Choices", &c.Choices) + delete(rawMsg, key) + case "created": + err = unpopulate(val, "Created", &c.Created) + delete(rawMsg, key) + case "id": + err = unpopulate(val, "ID", &c.ID) + delete(rawMsg, key) + case "prompt_annotations": + err = unpopulate(val, "PromptAnnotations", &c.PromptAnnotations) + delete(rawMsg, key) + case "usage": + err = unpopulate(val, "Usage", &c.Usage) + delete(rawMsg, key) + } + if err != nil { + return fmt.Errorf("unmarshalling type %T: %v", c, err) + } + } + return nil +} + +// MarshalJSON implements the json.Marshaller interface for type CompletionsLogProbabilityModel. +func (c CompletionsLogProbabilityModel) MarshalJSON() ([]byte, error) { + objectMap := make(map[string]any) + populate(objectMap, "text_offset", c.TextOffset) + populate(objectMap, "token_logprobs", c.TokenLogProbs) + populate(objectMap, "tokens", c.Tokens) + populate(objectMap, "top_logprobs", c.TopLogProbs) + return json.Marshal(objectMap) +} + +// UnmarshalJSON implements the json.Unmarshaller interface for type CompletionsLogProbabilityModel. +func (c *CompletionsLogProbabilityModel) UnmarshalJSON(data []byte) error { + var rawMsg map[string]json.RawMessage + if err := json.Unmarshal(data, &rawMsg); err != nil { + return fmt.Errorf("unmarshalling type %T: %v", c, err) + } + for key, val := range rawMsg { + var err error + switch key { + case "text_offset": + err = unpopulate(val, "TextOffset", &c.TextOffset) + delete(rawMsg, key) + case "token_logprobs": + err = unpopulate(val, "TokenLogProbs", &c.TokenLogProbs) + delete(rawMsg, key) + case "tokens": + err = unpopulate(val, "Tokens", &c.Tokens) + delete(rawMsg, key) + case "top_logprobs": + err = unpopulate(val, "TopLogProbs", &c.TopLogProbs) + delete(rawMsg, key) + } + if err != nil { + return fmt.Errorf("unmarshalling type %T: %v", c, err) + } + } + return nil +} + +// MarshalJSON implements the json.Marshaller interface for type CompletionsOptions. +func (c CompletionsOptions) MarshalJSON() ([]byte, error) { + objectMap := make(map[string]any) + populate(objectMap, "best_of", c.BestOf) + populate(objectMap, "echo", c.Echo) + populate(objectMap, "frequency_penalty", c.FrequencyPenalty) + populate(objectMap, "logit_bias", c.LogitBias) + populate(objectMap, "logprobs", c.LogProbs) + populate(objectMap, "max_tokens", c.MaxTokens) + populate(objectMap, "model", &c.DeploymentID) + populate(objectMap, "n", c.N) + populate(objectMap, "presence_penalty", c.PresencePenalty) + populate(objectMap, "prompt", c.Prompt) + populate(objectMap, "stop", c.Stop) + populate(objectMap, "temperature", c.Temperature) + populate(objectMap, "top_p", c.TopP) + populate(objectMap, "user", c.User) + return json.Marshal(objectMap) +} + +// UnmarshalJSON implements the json.Unmarshaller interface for type CompletionsOptions. +func (c *CompletionsOptions) UnmarshalJSON(data []byte) error { + var rawMsg map[string]json.RawMessage + if err := json.Unmarshal(data, &rawMsg); err != nil { + return fmt.Errorf("unmarshalling type %T: %v", c, err) + } + for key, val := range rawMsg { + var err error + switch key { + case "best_of": + err = unpopulate(val, "BestOf", &c.BestOf) + delete(rawMsg, key) + case "echo": + err = unpopulate(val, "Echo", &c.Echo) + delete(rawMsg, key) + case "frequency_penalty": + err = unpopulate(val, "FrequencyPenalty", &c.FrequencyPenalty) + delete(rawMsg, key) + case "logit_bias": + err = unpopulate(val, "LogitBias", &c.LogitBias) + delete(rawMsg, key) + case "logprobs": + err = unpopulate(val, "LogProbs", &c.LogProbs) + delete(rawMsg, key) + case "max_tokens": + err = unpopulate(val, "MaxTokens", &c.MaxTokens) + delete(rawMsg, key) + case "model": + err = unpopulate(val, "Model", &c.DeploymentID) + delete(rawMsg, key) + case "n": + err = unpopulate(val, "N", &c.N) + delete(rawMsg, key) + case "presence_penalty": + err = unpopulate(val, "PresencePenalty", &c.PresencePenalty) + delete(rawMsg, key) + case "prompt": + err = unpopulate(val, "Prompt", &c.Prompt) + delete(rawMsg, key) + case "stop": + err = unpopulate(val, "Stop", &c.Stop) + delete(rawMsg, key) + case "temperature": + err = unpopulate(val, "Temperature", &c.Temperature) + delete(rawMsg, key) + case "top_p": + err = unpopulate(val, "TopP", &c.TopP) + delete(rawMsg, key) + case "user": + err = unpopulate(val, "User", &c.User) + delete(rawMsg, key) + } + if err != nil { + return fmt.Errorf("unmarshalling type %T: %v", c, err) + } + } + return nil +} + +// MarshalJSON implements the json.Marshaller interface for type CompletionsUsage. +func (c CompletionsUsage) MarshalJSON() ([]byte, error) { + objectMap := make(map[string]any) + populate(objectMap, "completion_tokens", c.CompletionTokens) + populate(objectMap, "prompt_tokens", c.PromptTokens) + populate(objectMap, "total_tokens", c.TotalTokens) + return json.Marshal(objectMap) +} + +// UnmarshalJSON implements the json.Unmarshaller interface for type CompletionsUsage. +func (c *CompletionsUsage) UnmarshalJSON(data []byte) error { + var rawMsg map[string]json.RawMessage + if err := json.Unmarshal(data, &rawMsg); err != nil { + return fmt.Errorf("unmarshalling type %T: %v", c, err) + } + for key, val := range rawMsg { + var err error + switch key { + case "completion_tokens": + err = unpopulate(val, "CompletionTokens", &c.CompletionTokens) + delete(rawMsg, key) + case "prompt_tokens": + err = unpopulate(val, "PromptTokens", &c.PromptTokens) + delete(rawMsg, key) + case "total_tokens": + err = unpopulate(val, "TotalTokens", &c.TotalTokens) + delete(rawMsg, key) + } + if err != nil { + return fmt.Errorf("unmarshalling type %T: %v", c, err) + } + } + return nil +} + +// MarshalJSON implements the json.Marshaller interface for type ContentFilterResults. +func (c ContentFilterResults) MarshalJSON() ([]byte, error) { + objectMap := make(map[string]any) + populate(objectMap, "hate", c.Hate) + populate(objectMap, "self_harm", c.SelfHarm) + populate(objectMap, "sexual", c.Sexual) + populate(objectMap, "violence", c.Violence) + return json.Marshal(objectMap) +} + +// UnmarshalJSON implements the json.Unmarshaller interface for type ContentFilterResults. +func (c *ContentFilterResults) UnmarshalJSON(data []byte) error { + var rawMsg map[string]json.RawMessage + if err := json.Unmarshal(data, &rawMsg); err != nil { + return fmt.Errorf("unmarshalling type %T: %v", c, err) + } + for key, val := range rawMsg { + var err error + switch key { + case "hate": + err = unpopulate(val, "Hate", &c.Hate) + delete(rawMsg, key) + case "self_harm": + err = unpopulate(val, "SelfHarm", &c.SelfHarm) + delete(rawMsg, key) + case "sexual": + err = unpopulate(val, "Sexual", &c.Sexual) + delete(rawMsg, key) + case "violence": + err = unpopulate(val, "Violence", &c.Violence) + delete(rawMsg, key) + } + if err != nil { + return fmt.Errorf("unmarshalling type %T: %v", c, err) + } + } + return nil +} + +// MarshalJSON implements the json.Marshaller interface for type ContentFilterResultsHate. +func (c ContentFilterResultsHate) MarshalJSON() ([]byte, error) { + objectMap := make(map[string]any) + populate(objectMap, "filtered", c.Filtered) + populate(objectMap, "severity", c.Severity) + return json.Marshal(objectMap) +} + +// UnmarshalJSON implements the json.Unmarshaller interface for type ContentFilterResultsHate. +func (c *ContentFilterResultsHate) UnmarshalJSON(data []byte) error { + var rawMsg map[string]json.RawMessage + if err := json.Unmarshal(data, &rawMsg); err != nil { + return fmt.Errorf("unmarshalling type %T: %v", c, err) + } + for key, val := range rawMsg { + var err error + switch key { + case "filtered": + err = unpopulate(val, "Filtered", &c.Filtered) + delete(rawMsg, key) + case "severity": + err = unpopulate(val, "Severity", &c.Severity) + delete(rawMsg, key) + } + if err != nil { + return fmt.Errorf("unmarshalling type %T: %v", c, err) + } + } + return nil +} + +// MarshalJSON implements the json.Marshaller interface for type ContentFilterResultsSelfHarm. +func (c ContentFilterResultsSelfHarm) MarshalJSON() ([]byte, error) { + objectMap := make(map[string]any) + populate(objectMap, "filtered", c.Filtered) + populate(objectMap, "severity", c.Severity) + return json.Marshal(objectMap) +} + +// UnmarshalJSON implements the json.Unmarshaller interface for type ContentFilterResultsSelfHarm. +func (c *ContentFilterResultsSelfHarm) UnmarshalJSON(data []byte) error { + var rawMsg map[string]json.RawMessage + if err := json.Unmarshal(data, &rawMsg); err != nil { + return fmt.Errorf("unmarshalling type %T: %v", c, err) + } + for key, val := range rawMsg { + var err error + switch key { + case "filtered": + err = unpopulate(val, "Filtered", &c.Filtered) + delete(rawMsg, key) + case "severity": + err = unpopulate(val, "Severity", &c.Severity) + delete(rawMsg, key) + } + if err != nil { + return fmt.Errorf("unmarshalling type %T: %v", c, err) + } + } + return nil +} + +// MarshalJSON implements the json.Marshaller interface for type ContentFilterResultsSexual. +func (c ContentFilterResultsSexual) MarshalJSON() ([]byte, error) { + objectMap := make(map[string]any) + populate(objectMap, "filtered", c.Filtered) + populate(objectMap, "severity", c.Severity) + return json.Marshal(objectMap) +} + +// UnmarshalJSON implements the json.Unmarshaller interface for type ContentFilterResultsSexual. +func (c *ContentFilterResultsSexual) UnmarshalJSON(data []byte) error { + var rawMsg map[string]json.RawMessage + if err := json.Unmarshal(data, &rawMsg); err != nil { + return fmt.Errorf("unmarshalling type %T: %v", c, err) + } + for key, val := range rawMsg { + var err error + switch key { + case "filtered": + err = unpopulate(val, "Filtered", &c.Filtered) + delete(rawMsg, key) + case "severity": + err = unpopulate(val, "Severity", &c.Severity) + delete(rawMsg, key) + } + if err != nil { + return fmt.Errorf("unmarshalling type %T: %v", c, err) + } + } + return nil +} + +// MarshalJSON implements the json.Marshaller interface for type ContentFilterResultsViolence. +func (c ContentFilterResultsViolence) MarshalJSON() ([]byte, error) { + objectMap := make(map[string]any) + populate(objectMap, "filtered", c.Filtered) + populate(objectMap, "severity", c.Severity) + return json.Marshal(objectMap) +} + +// UnmarshalJSON implements the json.Unmarshaller interface for type ContentFilterResultsViolence. +func (c *ContentFilterResultsViolence) UnmarshalJSON(data []byte) error { + var rawMsg map[string]json.RawMessage + if err := json.Unmarshal(data, &rawMsg); err != nil { + return fmt.Errorf("unmarshalling type %T: %v", c, err) + } + for key, val := range rawMsg { + var err error + switch key { + case "filtered": + err = unpopulate(val, "Filtered", &c.Filtered) + delete(rawMsg, key) + case "severity": + err = unpopulate(val, "Severity", &c.Severity) + delete(rawMsg, key) + } + if err != nil { + return fmt.Errorf("unmarshalling type %T: %v", c, err) + } + } + return nil +} + +// MarshalJSON implements the json.Marshaller interface for type Deployment. +func (d Deployment) MarshalJSON() ([]byte, error) { + objectMap := make(map[string]any) + populate(objectMap, "deploymentId", d.DeploymentID) + return json.Marshal(objectMap) +} + +// UnmarshalJSON implements the json.Unmarshaller interface for type Deployment. +func (d *Deployment) UnmarshalJSON(data []byte) error { + var rawMsg map[string]json.RawMessage + if err := json.Unmarshal(data, &rawMsg); err != nil { + return fmt.Errorf("unmarshalling type %T: %v", d, err) + } + for key, val := range rawMsg { + var err error + switch key { + case "deploymentId": + err = unpopulate(val, "DeploymentID", &d.DeploymentID) + delete(rawMsg, key) + } + if err != nil { + return fmt.Errorf("unmarshalling type %T: %v", d, err) + } + } + return nil +} + +// MarshalJSON implements the json.Marshaller interface for type EmbeddingItem. +func (e EmbeddingItem) MarshalJSON() ([]byte, error) { + objectMap := make(map[string]any) + populate(objectMap, "embedding", e.Embedding) + populate(objectMap, "index", e.Index) + return json.Marshal(objectMap) +} + +// UnmarshalJSON implements the json.Unmarshaller interface for type EmbeddingItem. +func (e *EmbeddingItem) UnmarshalJSON(data []byte) error { + var rawMsg map[string]json.RawMessage + if err := json.Unmarshal(data, &rawMsg); err != nil { + return fmt.Errorf("unmarshalling type %T: %v", e, err) + } + for key, val := range rawMsg { + var err error + switch key { + case "embedding": + err = unpopulate(val, "Embedding", &e.Embedding) + delete(rawMsg, key) + case "index": + err = unpopulate(val, "Index", &e.Index) + delete(rawMsg, key) + } + if err != nil { + return fmt.Errorf("unmarshalling type %T: %v", e, err) + } + } + return nil +} + +// MarshalJSON implements the json.Marshaller interface for type Embeddings. +func (e Embeddings) MarshalJSON() ([]byte, error) { + objectMap := make(map[string]any) + populate(objectMap, "data", e.Data) + populate(objectMap, "usage", e.Usage) + return json.Marshal(objectMap) +} + +// UnmarshalJSON implements the json.Unmarshaller interface for type Embeddings. +func (e *Embeddings) UnmarshalJSON(data []byte) error { + var rawMsg map[string]json.RawMessage + if err := json.Unmarshal(data, &rawMsg); err != nil { + return fmt.Errorf("unmarshalling type %T: %v", e, err) + } + for key, val := range rawMsg { + var err error + switch key { + case "data": + err = unpopulate(val, "Data", &e.Data) + delete(rawMsg, key) + case "usage": + err = unpopulate(val, "Usage", &e.Usage) + delete(rawMsg, key) + } + if err != nil { + return fmt.Errorf("unmarshalling type %T: %v", e, err) + } + } + return nil +} + +// MarshalJSON implements the json.Marshaller interface for type EmbeddingsOptions. +func (e EmbeddingsOptions) MarshalJSON() ([]byte, error) { + objectMap := make(map[string]any) + populate(objectMap, "input", e.Input) + populate(objectMap, "model", &e.DeploymentID) + populate(objectMap, "user", e.User) + return json.Marshal(objectMap) +} + +// UnmarshalJSON implements the json.Unmarshaller interface for type EmbeddingsOptions. +func (e *EmbeddingsOptions) UnmarshalJSON(data []byte) error { + var rawMsg map[string]json.RawMessage + if err := json.Unmarshal(data, &rawMsg); err != nil { + return fmt.Errorf("unmarshalling type %T: %v", e, err) + } + for key, val := range rawMsg { + var err error + switch key { + case "input": + err = unpopulate(val, "Input", &e.Input) + delete(rawMsg, key) + case "model": + err = unpopulate(val, "Model", &e.DeploymentID) + delete(rawMsg, key) + case "user": + err = unpopulate(val, "User", &e.User) + delete(rawMsg, key) + } + if err != nil { + return fmt.Errorf("unmarshalling type %T: %v", e, err) + } + } + return nil +} + +// MarshalJSON implements the json.Marshaller interface for type EmbeddingsUsage. +func (e EmbeddingsUsage) MarshalJSON() ([]byte, error) { + objectMap := make(map[string]any) + populate(objectMap, "prompt_tokens", e.PromptTokens) + populate(objectMap, "total_tokens", e.TotalTokens) + return json.Marshal(objectMap) +} + +// UnmarshalJSON implements the json.Unmarshaller interface for type EmbeddingsUsage. +func (e *EmbeddingsUsage) UnmarshalJSON(data []byte) error { + var rawMsg map[string]json.RawMessage + if err := json.Unmarshal(data, &rawMsg); err != nil { + return fmt.Errorf("unmarshalling type %T: %v", e, err) + } + for key, val := range rawMsg { + var err error + switch key { + case "prompt_tokens": + err = unpopulate(val, "PromptTokens", &e.PromptTokens) + delete(rawMsg, key) + case "total_tokens": + err = unpopulate(val, "TotalTokens", &e.TotalTokens) + delete(rawMsg, key) + } + if err != nil { + return fmt.Errorf("unmarshalling type %T: %v", e, err) + } + } + return nil +} + +// MarshalJSON implements the json.Marshaller interface for type EmbeddingsUsageAutoGenerated. +func (e EmbeddingsUsageAutoGenerated) MarshalJSON() ([]byte, error) { + objectMap := make(map[string]any) + populate(objectMap, "prompt_tokens", e.PromptTokens) + populate(objectMap, "total_tokens", e.TotalTokens) + return json.Marshal(objectMap) +} + +// UnmarshalJSON implements the json.Unmarshaller interface for type EmbeddingsUsageAutoGenerated. +func (e *EmbeddingsUsageAutoGenerated) UnmarshalJSON(data []byte) error { + var rawMsg map[string]json.RawMessage + if err := json.Unmarshal(data, &rawMsg); err != nil { + return fmt.Errorf("unmarshalling type %T: %v", e, err) + } + for key, val := range rawMsg { + var err error + switch key { + case "prompt_tokens": + err = unpopulate(val, "PromptTokens", &e.PromptTokens) + delete(rawMsg, key) + case "total_tokens": + err = unpopulate(val, "TotalTokens", &e.TotalTokens) + delete(rawMsg, key) + } + if err != nil { + return fmt.Errorf("unmarshalling type %T: %v", e, err) + } + } + return nil +} + +// MarshalJSON implements the json.Marshaller interface for type FunctionCall. +func (f FunctionCall) MarshalJSON() ([]byte, error) { + objectMap := make(map[string]any) + populate(objectMap, "arguments", f.Arguments) + populate(objectMap, "name", f.Name) + return json.Marshal(objectMap) +} + +// UnmarshalJSON implements the json.Unmarshaller interface for type FunctionCall. +func (f *FunctionCall) UnmarshalJSON(data []byte) error { + var rawMsg map[string]json.RawMessage + if err := json.Unmarshal(data, &rawMsg); err != nil { + return fmt.Errorf("unmarshalling type %T: %v", f, err) + } + for key, val := range rawMsg { + var err error + switch key { + case "arguments": + err = unpopulate(val, "Arguments", &f.Arguments) + delete(rawMsg, key) + case "name": + err = unpopulate(val, "Name", &f.Name) + delete(rawMsg, key) + } + if err != nil { + return fmt.Errorf("unmarshalling type %T: %v", f, err) + } + } + return nil +} + +// MarshalJSON implements the json.Marshaller interface for type FunctionDefinition. +func (f FunctionDefinition) MarshalJSON() ([]byte, error) { + objectMap := make(map[string]any) + populate(objectMap, "description", f.Description) + populate(objectMap, "name", f.Name) + populateAny(objectMap, "parameters", f.Parameters) + return json.Marshal(objectMap) +} + +// UnmarshalJSON implements the json.Unmarshaller interface for type FunctionDefinition. +func (f *FunctionDefinition) UnmarshalJSON(data []byte) error { + var rawMsg map[string]json.RawMessage + if err := json.Unmarshal(data, &rawMsg); err != nil { + return fmt.Errorf("unmarshalling type %T: %v", f, err) + } + for key, val := range rawMsg { + var err error + switch key { + case "description": + err = unpopulate(val, "Description", &f.Description) + delete(rawMsg, key) + case "name": + err = unpopulate(val, "Name", &f.Name) + delete(rawMsg, key) + case "parameters": + err = unpopulate(val, "Parameters", &f.Parameters) + delete(rawMsg, key) + } + if err != nil { + return fmt.Errorf("unmarshalling type %T: %v", f, err) + } + } + return nil +} + +// MarshalJSON implements the json.Marshaller interface for type FunctionName. +func (f FunctionName) MarshalJSON() ([]byte, error) { + objectMap := make(map[string]any) + populate(objectMap, "name", f.Name) + return json.Marshal(objectMap) +} + +// UnmarshalJSON implements the json.Unmarshaller interface for type FunctionName. +func (f *FunctionName) UnmarshalJSON(data []byte) error { + var rawMsg map[string]json.RawMessage + if err := json.Unmarshal(data, &rawMsg); err != nil { + return fmt.Errorf("unmarshalling type %T: %v", f, err) + } + for key, val := range rawMsg { + var err error + switch key { + case "name": + err = unpopulate(val, "Name", &f.Name) + delete(rawMsg, key) + } + if err != nil { + return fmt.Errorf("unmarshalling type %T: %v", f, err) + } + } + return nil +} + +// MarshalJSON implements the json.Marshaller interface for type ImageGenerationOptions. +func (i ImageGenerationOptions) MarshalJSON() ([]byte, error) { + objectMap := make(map[string]any) + populate(objectMap, "n", i.N) + populate(objectMap, "prompt", i.Prompt) + populate(objectMap, "response_format", i.ResponseFormat) + populate(objectMap, "size", i.Size) + populate(objectMap, "user", i.User) + return json.Marshal(objectMap) +} + +// UnmarshalJSON implements the json.Unmarshaller interface for type ImageGenerationOptions. +func (i *ImageGenerationOptions) UnmarshalJSON(data []byte) error { + var rawMsg map[string]json.RawMessage + if err := json.Unmarshal(data, &rawMsg); err != nil { + return fmt.Errorf("unmarshalling type %T: %v", i, err) + } + for key, val := range rawMsg { + var err error + switch key { + case "n": + err = unpopulate(val, "N", &i.N) + delete(rawMsg, key) + case "prompt": + err = unpopulate(val, "Prompt", &i.Prompt) + delete(rawMsg, key) + case "response_format": + err = unpopulate(val, "ResponseFormat", &i.ResponseFormat) + delete(rawMsg, key) + case "size": + err = unpopulate(val, "Size", &i.Size) + delete(rawMsg, key) + case "user": + err = unpopulate(val, "User", &i.User) + delete(rawMsg, key) + } + if err != nil { + return fmt.Errorf("unmarshalling type %T: %v", i, err) + } + } + return nil +} + +// MarshalJSON implements the json.Marshaller interface for type ImageGenerations. +func (i ImageGenerations) MarshalJSON() ([]byte, error) { + objectMap := make(map[string]any) + populate(objectMap, "created", i.Created) + populateAny(objectMap, "data", i.Data) + return json.Marshal(objectMap) +} + +// UnmarshalJSON implements the json.Unmarshaller interface for type ImageGenerations. +func (i *ImageGenerations) UnmarshalJSON(data []byte) error { + var rawMsg map[string]json.RawMessage + if err := json.Unmarshal(data, &rawMsg); err != nil { + return fmt.Errorf("unmarshalling type %T: %v", i, err) + } + for key, val := range rawMsg { + var err error + switch key { + case "created": + err = unpopulate(val, "Created", &i.Created) + delete(rawMsg, key) + case "data": + err = unpopulate(val, "Data", &i.Data) + delete(rawMsg, key) + } + if err != nil { + return fmt.Errorf("unmarshalling type %T: %v", i, err) + } + } + return nil +} + +// MarshalJSON implements the json.Marshaller interface for type PromptFilterResult. +func (p PromptFilterResult) MarshalJSON() ([]byte, error) { + objectMap := make(map[string]any) + populate(objectMap, "content_filter_results", p.ContentFilterResults) + populate(objectMap, "prompt_index", p.PromptIndex) + return json.Marshal(objectMap) +} + +// UnmarshalJSON implements the json.Unmarshaller interface for type PromptFilterResult. +func (p *PromptFilterResult) UnmarshalJSON(data []byte) error { + var rawMsg map[string]json.RawMessage + if err := json.Unmarshal(data, &rawMsg); err != nil { + return fmt.Errorf("unmarshalling type %T: %v", p, err) + } + for key, val := range rawMsg { + var err error + switch key { + case "content_filter_results": + err = unpopulate(val, "ContentFilterResults", &p.ContentFilterResults) + delete(rawMsg, key) + case "prompt_index": + err = unpopulate(val, "PromptIndex", &p.PromptIndex) + delete(rawMsg, key) + } + if err != nil { + return fmt.Errorf("unmarshalling type %T: %v", p, err) + } + } + return nil +} + +// MarshalJSON implements the json.Marshaller interface for type PromptFilterResultContentFilterResults. +func (p PromptFilterResultContentFilterResults) MarshalJSON() ([]byte, error) { + objectMap := make(map[string]any) + populate(objectMap, "hate", p.Hate) + populate(objectMap, "self_harm", p.SelfHarm) + populate(objectMap, "sexual", p.Sexual) + populate(objectMap, "violence", p.Violence) + return json.Marshal(objectMap) +} + +// UnmarshalJSON implements the json.Unmarshaller interface for type PromptFilterResultContentFilterResults. +func (p *PromptFilterResultContentFilterResults) UnmarshalJSON(data []byte) error { + var rawMsg map[string]json.RawMessage + if err := json.Unmarshal(data, &rawMsg); err != nil { + return fmt.Errorf("unmarshalling type %T: %v", p, err) + } + for key, val := range rawMsg { + var err error + switch key { + case "hate": + err = unpopulate(val, "Hate", &p.Hate) + delete(rawMsg, key) + case "self_harm": + err = unpopulate(val, "SelfHarm", &p.SelfHarm) + delete(rawMsg, key) + case "sexual": + err = unpopulate(val, "Sexual", &p.Sexual) + delete(rawMsg, key) + case "violence": + err = unpopulate(val, "Violence", &p.Violence) + delete(rawMsg, key) + } + if err != nil { + return fmt.Errorf("unmarshalling type %T: %v", p, err) + } + } + return nil +} + +func populate(m map[string]any, k string, v any) { + if v == nil { + return + } else if azcore.IsNullValue(v) { + m[k] = nil + } else if !reflect.ValueOf(v).IsNil() { + m[k] = v + } +} + +func populateAny(m map[string]any, k string, v any) { + if v == nil { + return + } else if azcore.IsNullValue(v) { + m[k] = nil + } else { + m[k] = v + } +} + +func unpopulate(data json.RawMessage, fn string, v any) error { + if data == nil { + return nil + } + if err := json.Unmarshal(data, v); err != nil { + return fmt.Errorf("struct field %s: %v", fn, err) + } + return nil +} diff --git a/sdk/ai/azopenai/options.go b/sdk/ai/azopenai/options.go new file mode 100644 index 000000000000..9b2846429919 --- /dev/null +++ b/sdk/ai/azopenai/options.go @@ -0,0 +1,31 @@ +//go:build go1.18 +// +build go1.18 + +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// Code generated by Microsoft (R) AutoRest Code Generator. DO NOT EDIT. +// Changes may cause incorrect behavior and will be lost if the code is regenerated. + +package azopenai + +// beginAzureBatchImageGenerationOptions contains the optional parameters for the Client.beginAzureBatchImageGeneration +// method. +type beginAzureBatchImageGenerationOptions struct { + // Resumes the LRO from the provided token. + ResumeToken string +} + +// GetChatCompletionsOptions contains the optional parameters for the Client.GetChatCompletions method. +type GetChatCompletionsOptions struct { + // placeholder for future optional parameters +} + +// GetCompletionsOptions contains the optional parameters for the Client.GetCompletions method. +type GetCompletionsOptions struct { + // placeholder for future optional parameters +} + +// GetEmbeddingsOptions contains the optional parameters for the Client.GetEmbeddings method. +type GetEmbeddingsOptions struct { + // placeholder for future optional parameters +} diff --git a/sdk/ai/azopenai/policy_apikey.go b/sdk/ai/azopenai/policy_apikey.go new file mode 100644 index 000000000000..5231bec83f27 --- /dev/null +++ b/sdk/ai/azopenai/policy_apikey.go @@ -0,0 +1,46 @@ +//go:build go1.18 +// +build go1.18 + +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +package azopenai + +import ( + "net/http" + + "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy" +) + +// KeyCredential is used when doing APIKey-based authentication. +type KeyCredential struct { + // apiKey is the api key for the client. + apiKey string +} + +// NewKeyCredential creates a KeyCredential containing an API key for +// either Azure OpenAI or OpenAI. +func NewKeyCredential(apiKey string) (KeyCredential, error) { + return KeyCredential{apiKey: apiKey}, nil +} + +// apiKeyPolicy authorizes requests with an API key acquired from a KeyCredential. +type apiKeyPolicy struct { + header string + cred KeyCredential +} + +// newAPIKeyPolicy creates a policy object that authorizes requests with an API Key. +// cred: a KeyCredential implementation. +func newAPIKeyPolicy(cred KeyCredential, header string) *apiKeyPolicy { + return &apiKeyPolicy{ + header: header, + cred: cred, + } +} + +// Do returns a function which authorizes req with a token from the policy's credential +func (b *apiKeyPolicy) Do(req *policy.Request) (*http.Response, error) { + req.Raw().Header.Set(b.header, b.cred.apiKey) + return req.Next() +} diff --git a/sdk/ai/azopenai/policy_apikey_test.go b/sdk/ai/azopenai/policy_apikey_test.go new file mode 100644 index 000000000000..9c0ea799c575 --- /dev/null +++ b/sdk/ai/azopenai/policy_apikey_test.go @@ -0,0 +1,84 @@ +//go:build go1.18 +// +build go1.18 + +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +package azopenai + +import ( + "context" + "net/http" + "reflect" + "testing" + + "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime" + "github.com/stretchr/testify/require" + + "github.com/Azure/azure-sdk-for-go/sdk/internal/mock" +) + +func TestNewAPIKeyPolicy(t *testing.T) { + type args struct { + header string + cred KeyCredential + } + simpleCred, err := NewKeyCredential("apiKey") + require.NoError(t, err) + + simpleHeader := "headerName" + tests := []struct { + name string + args args + want *apiKeyPolicy + }{ + { + name: "simple", + args: args{ + cred: simpleCred, + header: simpleHeader, + }, + want: &apiKeyPolicy{ + header: simpleHeader, + cred: simpleCred, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := newAPIKeyPolicy(tt.args.cred, tt.args.header); !reflect.DeepEqual(got, tt.want) { + t.Errorf("NewAPIKeyPolicy() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestAPIKeyPolicy_Success(t *testing.T) { + srv, close := mock.NewTLSServer() + defer close() + srv.AppendResponse(mock.WithStatusCode(http.StatusOK)) + + cred, err := NewKeyCredential("secret") + require.NoError(t, err) + + authPolicy := newAPIKeyPolicy(cred, "api-key") + pipeline := runtime.NewPipeline( + "testmodule", + "v0.1.0", + runtime.PipelineOptions{PerRetry: []policy.Policy{authPolicy}}, + &policy.ClientOptions{ + Transport: srv, + }) + req, err := runtime.NewRequest(context.Background(), http.MethodGet, srv.URL()) + if err != nil { + t.Fatal(err) + } + resp, err := pipeline.Do(req) + if err != nil { + t.Fatalf("Expected nil error but received one") + } + if hdrValue := resp.Request.Header.Get("api-key"); hdrValue != "secret" { + t.Fatalf("expected api-key '%s', got '%s'", "secret", hdrValue) + } +} diff --git a/sdk/ai/azopenai/response_types.go b/sdk/ai/azopenai/response_types.go new file mode 100644 index 000000000000..0cf6c589ff85 --- /dev/null +++ b/sdk/ai/azopenai/response_types.go @@ -0,0 +1,39 @@ +//go:build go1.18 +// +build go1.18 + +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// Code generated by Microsoft (R) AutoRest Code Generator. DO NOT EDIT. +// Changes may cause incorrect behavior and will be lost if the code is regenerated. + +package azopenai + +// azureBatchImageGenerationInternalResponse contains the response from method Client.beginAzureBatchImageGeneration. +type azureBatchImageGenerationInternalResponse struct { + // A polling status update or final response payload for an image operation. + batchImageGenerationOperationResponse +} + +// GetChatCompletionsResponse contains the response from method Client.GetChatCompletions. +type GetChatCompletionsResponse struct { + // Representation of the response data from a chat completions request. + // Completions support a wide variety of tasks and generate text that continues from or "completes" + // provided prompt data. + ChatCompletions +} + +// GetCompletionsResponse contains the response from method Client.GetCompletions. +type GetCompletionsResponse struct { + // Representation of the response data from a completions request. + // Completions support a wide variety of tasks and generate text that continues from or "completes" + // provided prompt data. + Completions +} + +// GetEmbeddingsResponse contains the response from method Client.GetEmbeddings. +type GetEmbeddingsResponse struct { + // Representation of the response data from an embeddings request. + // Embeddings measure the relatedness of text strings and are commonly used for search, clustering, + // recommendations, and other similar scenarios. + Embeddings +} diff --git a/sdk/ai/azopenai/sample.env b/sdk/ai/azopenai/sample.env new file mode 100644 index 000000000000..0a12cbb0ac5a --- /dev/null +++ b/sdk/ai/azopenai/sample.env @@ -0,0 +1,19 @@ +# Azure OpenAI +AOAI_ENDPOINT=https://.openai.azure.com/ +AOAI_API_KEY= + +# These names will come from your model deployments in your Azure OpenAI resource +# ex: text-davinci-003 +AOAI_COMPLETIONS_MODEL_DEPLOYMENT= +# ex: gpt-4 +AOAI_CHAT_COMPLETIONS_MODEL_DEPLOYMENT= +AOAI_EMBEDDINGS_MODEL_DEPLOYMENT= + +# public OpenAI +OPENAI_ENDPOINT=https://api.openai.com/v1 +OPENAI_API_KEY= +# ex: text-davinci-003 +OPENAI_COMPLETIONS_MODEL= +# ex: gpt-4 +OPENAI_CHAT_COMPLETIONS_MODEL= +OPENAI_EMBEDDINGS_MODEL= \ No newline at end of file diff --git a/sdk/ai/azopenai/testdata/.gitignore b/sdk/ai/azopenai/testdata/.gitignore new file mode 100644 index 000000000000..75254e2c4d3c --- /dev/null +++ b/sdk/ai/azopenai/testdata/.gitignore @@ -0,0 +1,3 @@ +node_modules +generated +TempTypeSpecFiles \ No newline at end of file diff --git a/sdk/ai/azopenai/testdata/content_filter_response_error.json b/sdk/ai/azopenai/testdata/content_filter_response_error.json new file mode 100644 index 000000000000..709a49a6151c --- /dev/null +++ b/sdk/ai/azopenai/testdata/content_filter_response_error.json @@ -0,0 +1,30 @@ +{ + "error": { + "message": "The response was filtered due to the prompt triggering Azure OpenAI’s content management policy. Please modify your prompt and retry. To learn more about our content filtering policies please read our documentation: https://go.microsoft.com/fwlink/?linkid=2198766", + "type": null, + "param": "prompt", + "code": "content_filter", + "status": 400, + "innererror": { + "code": "ResponsibleAIPolicyViolation", + "content_filter_result": { + "hate": { + "filtered": false, + "severity": "safe" + }, + "self_harm": { + "filtered": false, + "severity": "safe" + }, + "sexual": { + "filtered": false, + "severity": "safe" + }, + "violence": { + "filtered": true, + "severity": "medium" + } + } + } + } +} \ No newline at end of file diff --git a/sdk/ai/azopenai/testdata/package-lock.json b/sdk/ai/azopenai/testdata/package-lock.json new file mode 100644 index 000000000000..6cdfadef100e --- /dev/null +++ b/sdk/ai/azopenai/testdata/package-lock.json @@ -0,0 +1,1009 @@ +{ + "name": "testdata", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "testdata", + "version": "0.1.0", + "dependencies": { + "@azure-tools/typespec-autorest": "^0.31.0", + "@azure-tools/typespec-azure-core": "^0.31.0", + "@typespec/compiler": "latest", + "@typespec/openapi3": "^0.45.0" + } + }, + "node_modules/@azure-tools/typespec-autorest": { + "version": "0.31.0", + "resolved": "https://registry.npmjs.org/@azure-tools/typespec-autorest/-/typespec-autorest-0.31.0.tgz", + "integrity": "sha512-l/C4HyGr0ByC7FnlsoorXDIp46pbDxVPbq59XNX9sKJJ8p2297BJv7FdPlLi0BXGjEmzy93Ag4hoH9H/u54AhQ==", + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "@azure-tools/typespec-azure-core": "~0.31.0", + "@typespec/compiler": "~0.45.0", + "@typespec/http": "~0.45.0", + "@typespec/openapi": "~0.45.0", + "@typespec/rest": "~0.45.0", + "@typespec/versioning": "~0.45.0" + } + }, + "node_modules/@azure-tools/typespec-azure-core": { + "version": "0.31.0", + "resolved": "https://registry.npmjs.org/@azure-tools/typespec-azure-core/-/typespec-azure-core-0.31.0.tgz", + "integrity": "sha512-sfJyRKGzQeBAm0Tw/CWFnWnHnxZDVbkXXLHeLb76VbRwkAu1P65eENRXXQTkUX5+PxnQH7qU/3MD5WT42AFsyA==", + "dependencies": { + "@typespec/lint": "~0.45.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "@typespec/compiler": "~0.45.0", + "@typespec/http": "~0.45.0", + "@typespec/rest": "~0.45.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.21.4.tgz", + "integrity": "sha512-LYvhNKfwWSPpocw8GI7gpK2nq3HSDuEPC/uSYaALSJu9xjsalaaYFOq0Pwt5KmVqwEbZlDu81aLXwBOmD/Fv9g==", + "dependencies": { + "@babel/highlight": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz", + "integrity": "sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.5.tgz", + "integrity": "sha512-BSKlD1hgnedS5XRnGOljZawtag7H1yPfQp0tdNJCHoH6AZ+Pcm9VvkrK59/Yy593Ypg0zMxH2BxD1VPYUQ7UIw==", + "dependencies": { + "@babel/helper-validator-identifier": "^7.22.5", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@typespec/compiler": { + "version": "0.45.2", + "resolved": "https://registry.npmjs.org/@typespec/compiler/-/compiler-0.45.2.tgz", + "integrity": "sha512-Te2mj24Sh0MinXPzPLINXRrjMuvuu2AsrBWrDWYjTgodt6MMRj5HiovxByFcYIjWUL3U4sHdKPXcOnAunDsd+Q==", + "dependencies": { + "@babel/code-frame": "~7.21.4", + "ajv": "~8.12.0", + "change-case": "~4.1.2", + "globby": "~13.1.1", + "js-yaml": "~4.1.0", + "mkdirp": "~2.1.6", + "mustache": "~4.2.0", + "node-watch": "~0.7.1", + "picocolors": "~1.0.0", + "prettier": "~2.8.7", + "prompts": "~2.4.1", + "vscode-languageserver": "~8.1.0", + "vscode-languageserver-textdocument": "~1.0.1", + "yargs": "~17.7.1" + }, + "bin": { + "tsp": "cmd/tsp.js", + "tsp-server": "cmd/tsp-server.js" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@typespec/http": { + "version": "0.45.0", + "resolved": "https://registry.npmjs.org/@typespec/http/-/http-0.45.0.tgz", + "integrity": "sha512-D9B+CzDqoIvlerQL5R7k367R5pwvX5Ic/6YE3bkMzfq9G40TRz5ExpOf+ASmgRbKrWjm/0ppdE4IlRMCI6qFmA==", + "peer": true, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "@typespec/compiler": "~0.45.0" + } + }, + "node_modules/@typespec/lint": { + "version": "0.45.0", + "resolved": "https://registry.npmjs.org/@typespec/lint/-/lint-0.45.0.tgz", + "integrity": "sha512-dACuEDQD1CFLftiKIcbWrARMb7lKEXMKE+GzsSa39Xogxv4FH6wc932tPwcMXXTdpfDO2dWUoquEcvXHxUAYKQ==", + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "@typespec/compiler": "~0.45.0" + } + }, + "node_modules/@typespec/openapi": { + "version": "0.45.0", + "resolved": "https://registry.npmjs.org/@typespec/openapi/-/openapi-0.45.0.tgz", + "integrity": "sha512-6W6DMCiXb2iLH4TLgI/u8FFS5v/oBu0DZF2BER6Pzx6v5mURmYGjXiwrQ+DrkOXtqb0YLZMuDU1s9CXQe6P87Q==", + "peer": true, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "@typespec/compiler": "~0.45.0", + "@typespec/http": "~0.45.0", + "@typespec/rest": "~0.45.0" + } + }, + "node_modules/@typespec/openapi3": { + "version": "0.45.0", + "resolved": "https://registry.npmjs.org/@typespec/openapi3/-/openapi3-0.45.0.tgz", + "integrity": "sha512-D5zBr88a+jpYFRGt01tGR0PiiLE6tazyAvayQ3+MCByZbHeQ+YDCmKgf6lbGtHbN4p1FqBhK+JrzH/42cXHYEw==", + "dependencies": { + "js-yaml": "~4.1.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "@typespec/compiler": "~0.45.0", + "@typespec/http": "~0.45.0", + "@typespec/openapi": "~0.45.0", + "@typespec/rest": "~0.45.0", + "@typespec/versioning": "~0.45.0" + } + }, + "node_modules/@typespec/rest": { + "version": "0.45.0", + "resolved": "https://registry.npmjs.org/@typespec/rest/-/rest-0.45.0.tgz", + "integrity": "sha512-u9vFmXvoKdkffh0I2LDDPAKNpILuaxu/aaMRdLEw1Zfmes2mWDruReMjPU8piRB+hE5eDDxU4INPtudy2D61tA==", + "peer": true, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "@typespec/compiler": "~0.45.0" + } + }, + "node_modules/@typespec/versioning": { + "version": "0.45.0", + "resolved": "https://registry.npmjs.org/@typespec/versioning/-/versioning-0.45.0.tgz", + "integrity": "sha512-Hocyi9AAuPu9az7Aw4GiWk5CUhq9CQBx8KEDVqIEBI1AvZACPNqJU02TClSyMSKWZY6V/2Gb8lxvPyNvyiF8Hw==", + "peer": true, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "@typespec/compiler": "~0.45.0" + } + }, + "node_modules/ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/camel-case": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz", + "integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==", + "dependencies": { + "pascal-case": "^3.1.2", + "tslib": "^2.0.3" + } + }, + "node_modules/capital-case": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/capital-case/-/capital-case-1.0.4.tgz", + "integrity": "sha512-ds37W8CytHgwnhGGTi88pcPyR15qoNkOpYwmMMfnWqqWgESapLqvDx6huFjQ5vqWSn2Z06173XNA7LtMOeUh1A==", + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3", + "upper-case-first": "^2.0.2" + } + }, + "node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/change-case": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/change-case/-/change-case-4.1.2.tgz", + "integrity": "sha512-bSxY2ws9OtviILG1EiY5K7NNxkqg/JnRnFxLtKQ96JaviiIxi7djMrSd0ECT9AC+lttClmYwKw53BWpOMblo7A==", + "dependencies": { + "camel-case": "^4.1.2", + "capital-case": "^1.0.4", + "constant-case": "^3.0.4", + "dot-case": "^3.0.4", + "header-case": "^2.0.4", + "no-case": "^3.0.4", + "param-case": "^3.0.4", + "pascal-case": "^3.1.2", + "path-case": "^3.0.4", + "sentence-case": "^3.0.4", + "snake-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + }, + "node_modules/constant-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/constant-case/-/constant-case-3.0.4.tgz", + "integrity": "sha512-I2hSBi7Vvs7BEuJDr5dDHfzb/Ruj3FyvFyh7KLilAjNQw3Be+xgqUBA2W6scVEcL0hL1dwPRtIqEPVUCKkSsyQ==", + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3", + "upper-case": "^2.0.2" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/dot-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", + "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + }, + "node_modules/fast-glob": { + "version": "3.2.12", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", + "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fastq": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", + "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/globby": { + "version": "13.1.4", + "resolved": "https://registry.npmjs.org/globby/-/globby-13.1.4.tgz", + "integrity": "sha512-iui/IiiW+QrJ1X1hKH5qwlMQyv34wJAYwH1vrf8b9kBA4sNiif3gKsMHa+BrdnOpEudWjpotfa7LrTzB1ERS/g==", + "dependencies": { + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.11", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "engines": { + "node": ">=4" + } + }, + "node_modules/header-case": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/header-case/-/header-case-2.0.4.tgz", + "integrity": "sha512-H/vuk5TEEVZwrR0lp2zed9OCo1uAILMlx0JEMgC26rzyJJ3N1v6XkwHHXJQdR2doSjcGPM6OKPYoJgf0plJ11Q==", + "dependencies": { + "capital-case": "^1.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/ignore": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", + "engines": { + "node": ">= 4" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + }, + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "engines": { + "node": ">=6" + } + }, + "node_modules/lower-case": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", + "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", + "dependencies": { + "tslib": "^2.0.3" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mkdirp": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-2.1.6.tgz", + "integrity": "sha512-+hEnITedc8LAtIP9u3HJDFIdcLV2vXP33sqLLIzkv1Db1zO/1OxbvYf0Y1OC/S/Qo5dxHXepofhmxL02PsKe+A==", + "bin": { + "mkdirp": "dist/cjs/src/bin.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/mustache": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz", + "integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==", + "bin": { + "mustache": "bin/mustache" + } + }, + "node_modules/no-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", + "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", + "dependencies": { + "lower-case": "^2.0.2", + "tslib": "^2.0.3" + } + }, + "node_modules/node-watch": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/node-watch/-/node-watch-0.7.3.tgz", + "integrity": "sha512-3l4E8uMPY1HdMMryPRUAl+oIHtXtyiTlIiESNSVSNxcPfzAFzeTbXFQkZfAwBbo0B1qMSG8nUABx+Gd+YrbKrQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/param-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", + "integrity": "sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==", + "dependencies": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/pascal-case": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz", + "integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==", + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/path-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/path-case/-/path-case-3.0.4.tgz", + "integrity": "sha512-qO4qCFjXqVTrcbPt/hQfhTQ+VhFsqNKOPtytgNKkKxSoEp3XPUQ8ObFuePylOIok5gjn69ry8XiULxCwot3Wfg==", + "dependencies": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/prettier": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", + "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/punycode": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", + "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/sentence-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/sentence-case/-/sentence-case-3.0.4.tgz", + "integrity": "sha512-8LS0JInaQMCRoQ7YUytAo/xUu5W2XnQxV2HI/6uM6U7CITS1RqPElr30V6uIqyMKM9lJGRVFy5/4CuzcixNYSg==", + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3", + "upper-case-first": "^2.0.2" + } + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==" + }, + "node_modules/slash": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", + "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/snake-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/snake-case/-/snake-case-3.0.4.tgz", + "integrity": "sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==", + "dependencies": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/tslib": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", + "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==" + }, + "node_modules/upper-case": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/upper-case/-/upper-case-2.0.2.tgz", + "integrity": "sha512-KgdgDGJt2TpuwBUIjgG6lzw2GWFRCW9Qkfkiv0DxqHHLYJHmtmdUIKcZd8rHgFSjopVTlw6ggzCm1b8MFQwikg==", + "dependencies": { + "tslib": "^2.0.3" + } + }, + "node_modules/upper-case-first": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/upper-case-first/-/upper-case-first-2.0.2.tgz", + "integrity": "sha512-514ppYHBaKwfJRK/pNC6c/OxfGa0obSnAl106u97Ed0I625Nin96KAjttZF6ZL3e1XLtphxnqrOi9iWgm+u+bg==", + "dependencies": { + "tslib": "^2.0.3" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/vscode-jsonrpc": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.1.0.tgz", + "integrity": "sha512-6TDy/abTQk+zDGYazgbIPc+4JoXdwC8NHU9Pbn4UJP1fehUyZmM4RHp5IthX7A6L5KS30PRui+j+tbbMMMafdw==", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/vscode-languageserver": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-8.1.0.tgz", + "integrity": "sha512-eUt8f1z2N2IEUDBsKaNapkz7jl5QpskN2Y0G01T/ItMxBxw1fJwvtySGB9QMecatne8jFIWJGWI61dWjyTLQsw==", + "dependencies": { + "vscode-languageserver-protocol": "3.17.3" + }, + "bin": { + "installServerIntoExtension": "bin/installServerIntoExtension" + } + }, + "node_modules/vscode-languageserver-protocol": { + "version": "3.17.3", + "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.3.tgz", + "integrity": "sha512-924/h0AqsMtA5yK22GgMtCYiMdCOtWTSGgUOkgEDX+wk2b0x4sAfLiO4NxBxqbiVtz7K7/1/RgVrVI0NClZwqA==", + "dependencies": { + "vscode-jsonrpc": "8.1.0", + "vscode-languageserver-types": "3.17.3" + } + }, + "node_modules/vscode-languageserver-textdocument": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.8.tgz", + "integrity": "sha512-1bonkGqQs5/fxGT5UchTgjGVnfysL0O8v1AYMBjqTbWQTFn721zaPGDYFkOKtfDgFiSgXM3KwaG3FMGfW4Ed9Q==" + }, + "node_modules/vscode-languageserver-types": { + "version": "3.17.3", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.3.tgz", + "integrity": "sha512-SYU4z1dL0PyIMd4Vj8YOqFvHu7Hz/enbWtpfnVbJHU4Nd1YNYx8u0ennumc6h48GQNeOLxmwySmnADouT/AuZA==" + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/wrap-ansi/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "engines": { + "node": ">=12" + } + } + } +} diff --git a/sdk/ai/azopenai/testdata/package.json b/sdk/ai/azopenai/testdata/package.json new file mode 100644 index 000000000000..b11bb3836a51 --- /dev/null +++ b/sdk/ai/azopenai/testdata/package.json @@ -0,0 +1,16 @@ +{ + "name": "testdata", + "version": "0.1.0", + "type": "module", + "scripts": { + "pull": "pwsh ../../../../eng/common/scripts/TypeSpec-Project-Sync.ps1 -ProjectDirectory . && rm ./TempTypeSpecFiles/OpenAI.Inference/tspconfig.yaml", + "build": "tsp compile ./TempTypeSpecFiles/OpenAI.Inference" + }, + "dependencies": { + "@azure-tools/typespec-autorest": "^0.31.0", + "@azure-tools/typespec-azure-core": "^0.31.0", + "@typespec/compiler": "latest", + "@typespec/openapi3": "^0.45.0" + }, + "private": true +} \ No newline at end of file diff --git a/sdk/ai/azopenai/testdata/tsp-location.yaml b/sdk/ai/azopenai/testdata/tsp-location.yaml new file mode 100644 index 000000000000..f621a7595107 --- /dev/null +++ b/sdk/ai/azopenai/testdata/tsp-location.yaml @@ -0,0 +1,4 @@ +#location: https://github.com/Azure/azure-rest-api-specs/tree/1393b6e34d7370733e3e2236c4df686280a96f36/specification/cognitiveservices/OpenAI.Inference +directory: specification/cognitiveservices/OpenAI.Inference +commit: 812c8a0322c016efec774d5682797d5a40336131 +repo: Azure/azure-rest-api-specs \ No newline at end of file diff --git a/sdk/ai/azopenai/testdata/tspconfig.yaml b/sdk/ai/azopenai/testdata/tspconfig.yaml new file mode 100644 index 000000000000..8abb60e25a59 --- /dev/null +++ b/sdk/ai/azopenai/testdata/tspconfig.yaml @@ -0,0 +1,11 @@ +parameters: + "service-dir": + default: "sdk/openai" + "dependencies": + default: "" +emit: + - "@typespec/openapi3" +options: + "@typespec/openapi3": + emitter-output-dir: "{project-root}/generated" + output-file: "openapi3.json" diff --git a/sdk/ai/azopenai/version.go b/sdk/ai/azopenai/version.go new file mode 100644 index 000000000000..2fc33ee828c9 --- /dev/null +++ b/sdk/ai/azopenai/version.go @@ -0,0 +1,11 @@ +//go:build go1.18 +// +build go1.18 + +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +package azopenai + +const ( + version = "v0.1.1" +) diff --git a/sdk/cognitiveservices/azopenai/CHANGELOG.md b/sdk/cognitiveservices/azopenai/CHANGELOG.md index 17d13f93f73c..64b45a854194 100644 --- a/sdk/cognitiveservices/azopenai/CHANGELOG.md +++ b/sdk/cognitiveservices/azopenai/CHANGELOG.md @@ -1,14 +1,10 @@ # Release History -## 0.1.1 (Unreleased) - -### Features Added +## 0.1.1 (2023-07-26) ### Breaking Changes -### Bugs Fixed - -### Other Changes +* This module has moved to `github.com/Azure/azure-sdk-for-go/sdk/ai/azopenai`. ## 0.1.0 (2023-07-20) diff --git a/sdk/cognitiveservices/azopenai/README.md b/sdk/cognitiveservices/azopenai/README.md index 8d599d44b437..9f68baa97232 100644 --- a/sdk/cognitiveservices/azopenai/README.md +++ b/sdk/cognitiveservices/azopenai/README.md @@ -1,98 +1 @@ -# Azure OpenAI client module for Go - -NOTE: this client can be used with Azure OpenAI and OpenAI. - -Azure OpenAI Service provides access to OpenAI's powerful language models including the GPT-4, GPT-35-Turbo, and Embeddings model series, as well as image generation using DALL-E. - -[Source code][azopenai_repo] | [Package (pkg.go.dev)][azopenai_pkg_go] | [REST API documentation][openai_rest_docs] | [Product documentation][openai_docs] - -## Getting started - -### Prerequisites - -* Go, version 1.18 or higher - [Install Go](https://go.dev/doc/install) -* [Azure subscription][azure_sub] -* [Azure OpenAI access][azure_openai_access] - -### Install the packages - -Install the `azopenai` and `azidentity` modules with `go get`: - -```bash -go get github.com/Azure/azure-sdk-for-go/sdk/cognitiveservices/azopenai - -# optional -go get github.com/Azure/azure-sdk-for-go/sdk/azidentity -``` - -The [azidentity][azure_identity] module is used for Azure Active Directory authentication with Azure OpenAI. - -### Authentication - -#### Azure OpenAI - -Azure OpenAI clients can authenticate using Azure Active Directory or with an API key: - -* Using Azure Active Directory, with a TokenCredential: [example](https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/sdk/cognitiveservices/azopenai#example-NewClient) -* Using an API key: [example](https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/sdk/cognitiveservices/azopenai#example-NewClientWithKeyCredential) - -#### OpenAI - -OpenAI supports connecting using an API key: [example](https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/sdk/cognitiveservices/azopenai#example-NewClientForOpenAI) - -## Key concepts - -See [Key concepts][openai_key_concepts] in the product documentation for more details about general concepts. - -# Examples - -Examples for various scenarios can be found on [pkg.go.dev](https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/sdk/cognitiveservices/azopenai#pkg-examples) or in the example*_test.go files in our GitHub repo for [azopenai](https://github.com/Azure/azure-sdk-for-go/blob/main/sdk/cognitiveservices/azopenai). - -## Troubleshooting - -### Error Handling - -All methods that send HTTP requests return `*azcore.ResponseError` when these requests fail. `ResponseError` has error details and the raw response from the service. - -### Logging - -This module uses the logging implementation in `azcore`. To turn on logging for all Azure SDK modules, set `AZURE_SDK_GO_LOGGING` to `all`. By default, the logger writes to stderr. Use the `azcore/log` package to control log output. For example, logging only HTTP request and response events, and printing them to stdout: - -```go -import azlog "github.com/Azure/azure-sdk-for-go/sdk/azcore/log" - -// Print log events to stdout -azlog.SetListener(func(cls azlog.Event, msg string) { - fmt.Println(msg) -}) - -// Includes only requests and responses in credential logs -azlog.SetEvents(azlog.EventRequest, azlog.EventResponse) -``` - -## Contributing - -This project welcomes contributions and suggestions. Most contributions require you to agree to a [Contributor License Agreement (CLA)][cla] declaring that you have the right to, and actually do, grant us the rights to use your contribution. - -When you submit a pull request, a CLA-bot will automatically determine whether you need to provide a CLA and decorate -the PR appropriately (e.g., label, comment). Simply follow the instructions provided by the bot. You will only need to -do this once across all repos using our CLA. - -This project has adopted the [Microsoft Open Source Code of Conduct][coc]. For more information, see -the [Code of Conduct FAQ][coc_faq] or contact [opencode@microsoft.com][coc_contact] with any additional questions or -comments. - - -[azure_openai_access]: https://learn.microsoft.com/azure/cognitive-services/openai/overview#how-do-i-get-access-to-azure-openai -[azopenai_repo]: https://github.com/Azure/azure-sdk-for-go/tree/main/sdk/cognitiveservices/azopenai -[azopenai_pkg_go]: https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/sdk/cognitiveservices/azopenai -[azure_identity]: https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/sdk/azidentity -[azure_sub]: https://azure.microsoft.com/free/ -[openai_docs]: https://learn.microsoft.com/azure/cognitive-services/openai -[openai_key_concepts]: https://learn.microsoft.com/azure/cognitive-services/openai/overview#key-concepts -[openai_rest_docs]: https://learn.microsoft.com/azure/cognitive-services/openai/reference -[cla]: https://cla.microsoft.com -[coc]: https://opensource.microsoft.com/codeofconduct/ -[coc_faq]: https://opensource.microsoft.com/codeofconduct/faq/ -[coc_contact]: mailto:opencode@microsoft.com -[azure_openai_quickstart]: https://learn.microsoft.com/azure/cognitive-services/openai/quickstart \ No newline at end of file +**Please note, this module is deprecated. Use `github.com/Azure/azure-sdk-for-go/sdk/ai/azopenai` instead** diff --git a/sdk/cognitiveservices/azopenai/go.mod b/sdk/cognitiveservices/azopenai/go.mod index c78eaaad2109..643ac1f034c1 100644 --- a/sdk/cognitiveservices/azopenai/go.mod +++ b/sdk/cognitiveservices/azopenai/go.mod @@ -1,3 +1,4 @@ +// Deprecated: use github.com/Azure/azure-sdk-for-go/sdk/ai/azopenai instead module github.com/Azure/azure-sdk-for-go/sdk/cognitiveservices/azopenai go 1.18