{"id":13465477,"url":"https://github.com/openai/openai-dotnet","last_synced_at":"2025-05-12T11:16:49.190Z","repository":{"id":243110112,"uuid":"792072603","full_name":"openai/openai-dotnet","owner":"openai","description":"The official .NET library for the OpenAI API","archived":false,"fork":false,"pushed_at":"2025-05-09T18:31:23.000Z","size":17096,"stargazers_count":2002,"open_issues_count":115,"forks_count":246,"subscribers_count":37,"default_branch":"main","last_synced_at":"2025-05-12T11:16:38.912Z","etag":null,"topics":["csharp","dotnet","openai"],"latest_commit_sha":null,"homepage":"https://www.nuget.org/packages/OpenAI","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/openai.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2024-04-25T23:27:40.000Z","updated_at":"2025-05-12T03:07:52.000Z","dependencies_parsed_at":"2024-08-29T23:11:04.455Z","dependency_job_id":"73cb3613-a4ac-4a3f-ad4c-2a4f043deb7e","html_url":"https://github.com/openai/openai-dotnet","commit_stats":{"total_commits":98,"total_committers":19,"mean_commits":5.157894736842105,"dds":0.6938775510204082,"last_synced_commit":"bbac88fd8f6a28c14e9dd05efc2519b491a2ef96"},"previous_names":["openai/openai-dotnet"],"tags_count":21,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/openai%2Fopenai-dotnet","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/openai%2Fopenai-dotnet/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/openai%2Fopenai-dotnet/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/openai%2Fopenai-dotnet/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/openai","download_url":"https://codeload.github.com/openai/openai-dotnet/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253726903,"owners_count":21954095,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["csharp","dotnet","openai"],"created_at":"2024-07-31T15:00:30.873Z","updated_at":"2025-05-12T11:16:49.158Z","avatar_url":"https://github.com/openai.png","language":"C#","readme":"# OpenAI .NET API library\n\n[![NuGet stable version](https://img.shields.io/nuget/v/openai.svg)](https://www.nuget.org/packages/OpenAI) [![NuGet preview version](https://img.shields.io/nuget/vpre/openai.svg)](https://www.nuget.org/packages/OpenAI/absoluteLatest)\n\nThe OpenAI .NET library provides convenient access to the OpenAI REST API from .NET applications.\n\nIt is generated from our [OpenAPI specification](https://github.com/openai/openai-openapi) in collaboration with Microsoft.\n\n## Table of Contents\n\n- [Getting started](#getting-started)\n  - [Prerequisites](#prerequisites)\n  - [Install the NuGet package](#install-the-nuget-package)\n- [Using the client library](#using-the-client-library)\n  - [Namespace organization](#namespace-organization)\n  - [Using the async API](#using-the-async-api)\n  - [Using the `OpenAIClient` class](#using-the-openaiclient-class)\n- [How to use chat completions with streaming](#how-to-use-chat-completions-with-streaming)\n- [How to use chat completions with tools and function calling](#how-to-use-chat-completions-with-tools-and-function-calling)\n- [How to use chat completions with structured outputs](#how-to-use-chat-completions-with-structured-outputs)\n- [How to use chat completions with audio](#how-to-use-chat-completions-with-audio)\n- [How to use responses with streaming and reasoning](#how-to-use-responses-with-streaming-and-reasoning)\n- [How to use responses with file search](#how-to-use-responses-with-file-search)\n- [How to use responses with web search](#how-to-use-responses-with-web-search)\n- [How to generate text embeddings](#how-to-generate-text-embeddings)\n- [How to generate images](#how-to-generate-images)\n- [How to transcribe audio](#how-to-transcribe-audio)\n- [How to use assistants with retrieval augmented generation (RAG)](#how-to-use-assistants-with-retrieval-augmented-generation-rag)\n- [How to use assistants with streaming and vision](#how-to-use-assistants-with-streaming-and-vision)\n- [How to work with Azure OpenAI](#how-to-work-with-azure-openai)\n- [Advanced scenarios](#advanced-scenarios)\n  - [Using protocol methods](#using-protocol-methods)\n  - [Mock a client for testing](#mock-a-client-for-testing)\n  - [Automatically retrying errors](#automatically-retrying-errors)\n  - [Observability](#observability)\n\n## Getting started\n\n### Prerequisites\n\nTo call the OpenAI REST API, you will need an API key. To obtain one, first [create a new OpenAI account](https://platform.openai.com/signup) or [log in](https://platform.openai.com/login). Next, navigate to the [API key page](https://platform.openai.com/account/api-keys) and select \"Create new secret key\", optionally naming the key. Make sure to save your API key somewhere safe and do not share it with anyone.\n\n### Install the NuGet package\n\nAdd the client library to your .NET project by installing the [NuGet](https://www.nuget.org/) package via your IDE or by running the following command in the .NET CLI:\n\n```cli\ndotnet add package OpenAI\n```\n\nIf you would like to try the latest preview version, remember to append the `--prerelease` command option.\n\nNote that the code examples included below were written using [.NET 8](https://dotnet.microsoft.com/download/dotnet/8.0). The OpenAI .NET library is compatible with all .NET Standard 2.0 applications, but the syntax used in some of the code examples in this document may depend on newer language features.\n\n## Using the client library\n\nThe full API of this library can be found in the [OpenAI.netstandard2.0.cs](https://github.com/openai/openai-dotnet/blob/main/api/OpenAI.netstandard2.0.cs) file, and there are many [code examples](https://github.com/openai/openai-dotnet/tree/main/examples) to help. For instance, the following snippet illustrates the basic use of the chat completions API:\n\n```csharp\nusing OpenAI.Chat;\n\nChatClient client = new(model: \"gpt-4o\", apiKey: Environment.GetEnvironmentVariable(\"OPENAI_API_KEY\"));\n\nChatCompletion completion = client.CompleteChat(\"Say 'this is a test.'\");\n\nConsole.WriteLine($\"[ASSISTANT]: {completion.Content[0].Text}\");\n```\n\nWhile you can pass your API key directly as a string, it is highly recommended that you keep it in a secure location and instead access it via an environment variable or configuration file as shown above to avoid storing it in source control.\n\n### Namespace organization\n\nThe library is organized into namespaces by feature areas in the OpenAI REST API. Each namespace contains a corresponding client class.\n\n| Namespace                     | Client class                 | Notes                                                             |\n| ------------------------------|------------------------------|-------------------------------------------------------------------|\n| `OpenAI.Assistants`           | `AssistantClient`            | ![Experimental](https://img.shields.io/badge/experimental-purple) |\n| `OpenAI.Audio`                | `AudioClient`                |                                                                   |\n| `OpenAI.Batch`                | `BatchClient`                | ![Experimental](https://img.shields.io/badge/experimental-purple) |\n| `OpenAI.Chat`                 | `ChatClient`                 |                                                                   |\n| `OpenAI.Embeddings`           | `EmbeddingClient`            |                                                                   |\n| `OpenAI.FineTuning`           | `FineTuningClient`           | ![Experimental](https://img.shields.io/badge/experimental-purple) |\n| `OpenAI.Files`                | `OpenAIFileClient`           |                                                                   |\n| `OpenAI.Images`               | `ImageClient`                |                                                                   |\n| `OpenAI.Models`               | `OpenAIModelClient`          |                                                                   |\n| `OpenAI.Moderations`          | `ModerationClient`           |                                                                   |\n| `OpenAI.Responses`            | `OpenAIResponseClient`       |                                                                   |\n| `OpenAI.VectorStores`         | `VectorStoreClient`          | ![Experimental](https://img.shields.io/badge/experimental-purple) |\n\n### Using the async API\n\nEvery client method that performs a synchronous API call has an asynchronous variant in the same client class. For instance, the asynchronous variant of the `ChatClient`'s `CompleteChat` method is `CompleteChatAsync`. To rewrite the call above using the asynchronous counterpart, simply `await` the call to the corresponding async variant:\n\n```csharp\nChatCompletion completion = await client.CompleteChatAsync(\"Say 'this is a test.'\");\n```\n\n### Using the `OpenAIClient` class\n\nIn addition to the namespaces mentioned above, there is also the parent `OpenAI` namespace itself:\n\n```csharp\nusing OpenAI;\n```\n\nThis namespace contains the `OpenAIClient` class, which offers certain conveniences when you need to work with multiple feature area clients. Specifically, you can use an instance of this class to create instances of the other clients and have them share the same implementation details, which might be more efficient.\n\nYou can create an `OpenAIClient` by specifying the API key that all clients will use for authentication:\n\n```csharp\nOpenAIClient client = new(Environment.GetEnvironmentVariable(\"OPENAI_API_KEY\"));\n```\n\nNext, to create an instance of an `AudioClient`, for example, you can call the `OpenAIClient`'s `GetAudioClient` method by passing the OpenAI model that the `AudioClient` will use, just as if you were using the `AudioClient` constructor directly. If necessary, you can create additional clients of the same type to target different models.\n\n```csharp\nAudioClient ttsClient = client.GetAudioClient(\"tts-1\");\nAudioClient whisperClient = client.GetAudioClient(\"whisper-1\");\n```\n\n## How to use chat completions with streaming\n\nWhen you request a chat completion, the default behavior is for the server to generate it in its entirety before sending it back in a single response. Consequently, long chat completions can require waiting for several seconds before hearing back from the server. To mitigate this, the OpenAI REST API supports the ability to stream partial results back as they are being generated, allowing you to start processing the beginning of the completion before it is finished.\n\nThe client library offers a convenient approach to working with streaming chat completions. If you wanted to re-write the example from the previous section using streaming, rather than calling the `ChatClient`'s `CompleteChat` method, you would call its `CompleteChatStreaming` method instead:\n\n```csharp\nCollectionResult\u003cStreamingChatCompletionUpdate\u003e completionUpdates = client.CompleteChatStreaming(\"Say 'this is a test.'\");\n```\n\nNotice that the returned value is a `CollectionResult\u003cStreamingChatCompletionUpdate\u003e` instance, which can be enumerated to process the streaming response chunks as they arrive:\n\n```csharp\nConsole.Write($\"[ASSISTANT]: \");\nforeach (StreamingChatCompletionUpdate completionUpdate in completionUpdates)\n{\n    if (completionUpdate.ContentUpdate.Count \u003e 0)\n    {\n        Console.Write(completionUpdate.ContentUpdate[0].Text);\n    }\n}\n```\n\nAlternatively, you can do this asynchronously by calling the `CompleteChatStreamingAsync` method to get an `AsyncCollectionResult\u003cStreamingChatCompletionUpdate\u003e` and enumerate it using `await foreach`:\n\n```csharp\nAsyncCollectionResult\u003cStreamingChatCompletionUpdate\u003e completionUpdates = client.CompleteChatStreamingAsync(\"Say 'this is a test.'\");\n\nConsole.Write($\"[ASSISTANT]: \");\nawait foreach (StreamingChatCompletionUpdate completionUpdate in completionUpdates)\n{\n    if (completionUpdate.ContentUpdate.Count \u003e 0)\n    {\n        Console.Write(completionUpdate.ContentUpdate[0].Text);\n    }\n}\n```\n\n## How to use chat completions with tools and function calling\n\nIn this example, you have two functions. The first function can retrieve a user's current geographic location (e.g., by polling the location service APIs of the user's device), while the second function can query the weather in a given location (e.g., by making an API call to some third-party weather service). You want the model to be able to call these functions if it deems it necessary to have this information in order to respond to a user request as part of generating a chat completion. For illustrative purposes, consider the following:\n\n```csharp\nprivate static string GetCurrentLocation()\n{\n    // Call the location API here.\n    return \"San Francisco\";\n}\n\nprivate static string GetCurrentWeather(string location, string unit = \"celsius\")\n{\n    // Call the weather API here.\n    return $\"31 {unit}\";\n}\n```\n\nStart by creating two `ChatTool` instances using the static `CreateFunctionTool` method to describe each function:\n\n```csharp\nprivate static readonly ChatTool getCurrentLocationTool = ChatTool.CreateFunctionTool(\n    functionName: nameof(GetCurrentLocation),\n    functionDescription: \"Get the user's current location\"\n);\n\nprivate static readonly ChatTool getCurrentWeatherTool = ChatTool.CreateFunctionTool(\n    functionName: nameof(GetCurrentWeather),\n    functionDescription: \"Get the current weather in a given location\",\n    functionParameters: BinaryData.FromBytes(\"\"\"\n        {\n            \"type\": \"object\",\n            \"properties\": {\n                \"location\": {\n                    \"type\": \"string\",\n                    \"description\": \"The city and state, e.g. Boston, MA\"\n                },\n                \"unit\": {\n                    \"type\": \"string\",\n                    \"enum\": [ \"celsius\", \"fahrenheit\" ],\n                    \"description\": \"The temperature unit to use. Infer this from the specified location.\"\n                }\n            },\n            \"required\": [ \"location\" ]\n        }\n        \"\"\"u8.ToArray())\n);\n```\n\nNext, create a `ChatCompletionOptions` instance and add both to its `Tools` property. You will pass the `ChatCompletionOptions` as an argument in your calls to the `ChatClient`'s `CompleteChat` method.\n\n```csharp\nList\u003cChatMessage\u003e messages = \n[\n    new UserChatMessage(\"What's the weather like today?\"),\n];\n\nChatCompletionOptions options = new()\n{\n    Tools = { getCurrentLocationTool, getCurrentWeatherTool },\n};\n```\n\nWhen the resulting `ChatCompletion` has a `FinishReason` property equal to `ChatFinishReason.ToolCalls`, it means that the model has determined that one or more tools must be called before the assistant can respond appropriately. In those cases, you must first call the function specified in the `ChatCompletion`'s `ToolCalls` and then call the `ChatClient`'s `CompleteChat` method again while passing the function's result as an additional `ChatRequestToolMessage`. Repeat this process as needed.\n\n```csharp\nbool requiresAction;\n\ndo\n{\n    requiresAction = false;\n    ChatCompletion completion = client.CompleteChat(messages, options);\n\n    switch (completion.FinishReason)\n    {\n        case ChatFinishReason.Stop:\n            {\n                // Add the assistant message to the conversation history.\n                messages.Add(new AssistantChatMessage(completion));\n                break;\n            }\n\n        case ChatFinishReason.ToolCalls:\n            {\n                // First, add the assistant message with tool calls to the conversation history.\n                messages.Add(new AssistantChatMessage(completion));\n\n                // Then, add a new tool message for each tool call that is resolved.\n                foreach (ChatToolCall toolCall in completion.ToolCalls)\n                {\n                    switch (toolCall.FunctionName)\n                    {\n                        case nameof(GetCurrentLocation):\n                            {\n                                string toolResult = GetCurrentLocation();\n                                messages.Add(new ToolChatMessage(toolCall.Id, toolResult));\n                                break;\n                            }\n\n                        case nameof(GetCurrentWeather):\n                            {\n                                // The arguments that the model wants to use to call the function are specified as a\n                                // stringified JSON object based on the schema defined in the tool definition. Note that\n                                // the model may hallucinate arguments too. Consequently, it is important to do the\n                                // appropriate parsing and validation before calling the function.\n                                using JsonDocument argumentsJson = JsonDocument.Parse(toolCall.FunctionArguments);\n                                bool hasLocation = argumentsJson.RootElement.TryGetProperty(\"location\", out JsonElement location);\n                                bool hasUnit = argumentsJson.RootElement.TryGetProperty(\"unit\", out JsonElement unit);\n\n                                if (!hasLocation)\n                                {\n                                    throw new ArgumentNullException(nameof(location), \"The location argument is required.\");\n                                }\n\n                                string toolResult = hasUnit\n                                    ? GetCurrentWeather(location.GetString(), unit.GetString())\n                                    : GetCurrentWeather(location.GetString());\n                                messages.Add(new ToolChatMessage(toolCall.Id, toolResult));\n                                break;\n                            }\n\n                        default:\n                            {\n                                // Handle other unexpected calls.\n                                throw new NotImplementedException();\n                            }\n                    }\n                }\n\n                requiresAction = true;\n                break;\n            }\n\n        case ChatFinishReason.Length:\n            throw new NotImplementedException(\"Incomplete model output due to MaxTokens parameter or token limit exceeded.\");\n\n        case ChatFinishReason.ContentFilter:\n            throw new NotImplementedException(\"Omitted content due to a content filter flag.\");\n\n        case ChatFinishReason.FunctionCall:\n            throw new NotImplementedException(\"Deprecated in favor of tool calls.\");\n\n        default:\n            throw new NotImplementedException(completion.FinishReason.ToString());\n    }\n} while (requiresAction);\n```\n\n## How to use chat completions with structured outputs\n\nBeginning with the `gpt-4o-mini`, `gpt-4o-mini-2024-07-18`, and `gpt-4o-2024-08-06` model snapshots, structured outputs are available for both top-level response content and tool calls in the chat completion and assistants APIs. For information about the feature, see [the Structured Outputs guide](https://platform.openai.com/docs/guides/structured-outputs/introduction).\n\nTo use structured outputs to constrain chat completion content, set an appropriate `ChatResponseFormat` as in the following example:\n\n```csharp\nList\u003cChatMessage\u003e messages =\n[\n    new UserChatMessage(\"How can I solve 8x + 7 = -23?\"),\n];\n\nChatCompletionOptions options = new()\n{\n    ResponseFormat = ChatResponseFormat.CreateJsonSchemaFormat(\n        jsonSchemaFormatName: \"math_reasoning\",\n        jsonSchema: BinaryData.FromBytes(\"\"\"\n            {\n                \"type\": \"object\",\n                \"properties\": {\n                \"steps\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                    \"type\": \"object\",\n                    \"properties\": {\n                        \"explanation\": { \"type\": \"string\" },\n                        \"output\": { \"type\": \"string\" }\n                    },\n                    \"required\": [\"explanation\", \"output\"],\n                    \"additionalProperties\": false\n                    }\n                },\n                \"final_answer\": { \"type\": \"string\" }\n                },\n                \"required\": [\"steps\", \"final_answer\"],\n                \"additionalProperties\": false\n            }\n            \"\"\"u8.ToArray()),\n        jsonSchemaIsStrict: true)\n};\n\nChatCompletion completion = client.CompleteChat(messages, options);\n\nusing JsonDocument structuredJson = JsonDocument.Parse(completion.Content[0].Text);\n\nConsole.WriteLine($\"Final answer: {structuredJson.RootElement.GetProperty(\"final_answer\")}\");\nConsole.WriteLine(\"Reasoning steps:\");\n\nforeach (JsonElement stepElement in structuredJson.RootElement.GetProperty(\"steps\").EnumerateArray())\n{\n    Console.WriteLine($\"  - Explanation: {stepElement.GetProperty(\"explanation\")}\");\n    Console.WriteLine($\"    Output: {stepElement.GetProperty(\"output\")}\");\n}\n```\n\n## How to use chat completions with audio\n\nStarting with the `gpt-4o-audio-preview` model, chat completions can process audio input and output.\n\nThis example demonstrates:\n  1. Configuring the client with the supported `gpt-4o-audio-preview` model\n  1. Supplying user audio input on a chat completion request\n  1. Requesting model audio output from the chat completion operation\n  1. Retrieving audio output from a `ChatCompletion` instance\n  1. Using past audio output as `ChatMessage` conversation history\n\n```csharp\n// Chat audio input and output is only supported on specific models, beginning with gpt-4o-audio-preview\nChatClient client = new(\"gpt-4o-audio-preview\", Environment.GetEnvironmentVariable(\"OPENAI_API_KEY\"));\n\n// Input audio is provided to a request by adding an audio content part to a user message\nstring audioFilePath = Path.Combine(\"Assets\", \"realtime_whats_the_weather_pcm16_24khz_mono.wav\");\nbyte[] audioFileRawBytes = File.ReadAllBytes(audioFilePath);\nBinaryData audioData = BinaryData.FromBytes(audioFileRawBytes);\nList\u003cChatMessage\u003e messages =\n    [\n        new UserChatMessage(ChatMessageContentPart.CreateInputAudioPart(audioData, ChatInputAudioFormat.Wav)),\n    ];\n\n// Output audio is requested by configuring ChatCompletionOptions to include the appropriate\n// ResponseModalities values and corresponding AudioOptions.\nChatCompletionOptions options = new()\n{\n    ResponseModalities = ChatResponseModalities.Text | ChatResponseModalities.Audio,\n    AudioOptions = new(ChatOutputAudioVoice.Alloy, ChatOutputAudioFormat.Mp3),\n};\n\nChatCompletion completion = client.CompleteChat(messages, options);\n\nvoid PrintAudioContent()\n{\n    if (completion.OutputAudio is ChatOutputAudio outputAudio)\n    {\n        Console.WriteLine($\"Response audio transcript: {outputAudio.Transcript}\");\n        string outputFilePath = $\"{outputAudio.Id}.mp3\";\n        using (FileStream outputFileStream = File.OpenWrite(outputFilePath))\n        {\n            outputFileStream.Write(outputAudio.AudioBytes);\n        }\n        Console.WriteLine($\"Response audio written to file: {outputFilePath}\");\n        Console.WriteLine($\"Valid on followup requests until: {outputAudio.ExpiresAt}\");\n    }\n}\n\nPrintAudioContent();\n\n// To refer to past audio output, create an assistant message from the earlier ChatCompletion, use the earlier\n// response content part, or use ChatMessageContentPart.CreateAudioPart(string) to manually instantiate a part.\n\nmessages.Add(new AssistantChatMessage(completion));\nmessages.Add(\"Can you say that like a pirate?\");\n\ncompletion = client.CompleteChat(messages, options);\n\nPrintAudioContent();\n```\n\nStreaming is highly parallel: `StreamingChatCompletionUpdate` instances can include a `OutputAudioUpdate` that may\ncontain any of:\n\n- The `Id` of the streamed audio content, which can be referenced by subsequent `AssistantChatMessage` instances via `ChatAudioReference` once the streaming response is complete; this may appear across multiple `StreamingChatCompletionUpdate` instances but will always be the same value when present\n- The `ExpiresAt` value that describes when the `Id` will no longer be valid for use with `ChatAudioReference` in subsequent requests; this typically appears once and only once, in the final `StreamingOutputAudioUpdate`\n- Incremental `TranscriptUpdate` and/or `AudioBytesUpdate` values, which can incrementally consumed and, when concatenated, form the complete audio transcript and audio output for the overall response; many of these typically appear\n\n## How to use responses with streaming and reasoning\n\n```csharp\nOpenAIResponseClient client = new(\n    model: \"o3-mini\",\n    apiKey: Environment.GetEnvironmentVariable(\"OPENAI_API_KEY\"));\n\nOpenAIResponse response = await client.CreateResponseAsync(\n    userInputText: \"What's the optimal strategy to win at poker?\",\n    new ResponseCreationOptions()\n    {\n        ReasoningOptions = new ResponseReasoningOptions()\n        {\n            ReasoningEffortLevel = ResponseReasoningEffortLevel.High,\n        },\n    });\n\nawait foreach (StreamingResponseUpdate update\n    in client.CreateResponseStreamingAsync(\n        userInputText: \"What's the optimal strategy to win at poker?\",\n        new ResponseCreationOptions()\n        {\n            ReasoningOptions = new ResponseReasoningOptions()\n            {\n                ReasoningEffortLevel = ResponseReasoningEffortLevel.High,\n            },\n        }))\n{\n    if (update is StreamingResponseItemUpdate itemUpdate\n        \u0026\u0026 itemUpdate.Item is ReasoningResponseItem reasoningItem)\n    {\n        Console.WriteLine($\"[Reasoning] ({reasoningItem.Status})\");\n    }\n    else if (update is StreamingResponseContentPartDeltaUpdate deltaUpdate)\n    {\n        Console.Write(deltaUpdate.Text);\n    }\n}\n```\n\n## How to use responses with file search\n\n```csharp\nOpenAIResponseClient client = new(\n    model: \"gpt-4o-mini\",\n    apiKey: Environment.GetEnvironmentVariable(\"OPENAI_API_KEY\"));\n\nResponseTool fileSearchTool\n    = ResponseTool.CreateFileSearchTool(\n        vectorStoreIds: [ExistingVectorStoreForTest.Id]);\nOpenAIResponse response = await client.CreateResponseAsync(\n    userInputText: \"According to available files, what's the secret number?\",\n    new ResponseCreationOptions()\n    {\n        Tools = { fileSearchTool }\n    });\n\nforeach (ResponseItem outputItem in response.OutputItems)\n{\n    if (outputItem is FileSearchCallResponseItem fileSearchCall)\n    {\n        Console.WriteLine($\"[file_search] ({fileSearchCall.Status}): {fileSearchCall.Id}\");\n        foreach (string query in fileSearchCall.Queries)\n        {\n            Console.WriteLine($\"  - {query}\");\n        }\n    }\n    else if (outputItem is MessageResponseItem message)\n    {\n        Console.WriteLine($\"[{message.Role}] {message.Content.FirstOrDefault()?.Text}\");\n    }\n}\n```\n\n## How to use responses with web search\n\n```csharp\nOpenAIResponseClient client = new(\n    model: \"gpt-4o-mini\",\n    apiKey: Environment.GetEnvironmentVariable(\"OPENAI_API_KEY\"));\n\nOpenAIResponse response = await client.CreateResponseAsync(\n    userInputText: \"What's a happy news headline from today?\",\n    new ResponseCreationOptions()\n    {\n        Tools = { ResponseTool.CreateWebSearchTool() },\n    });\n\nforeach (ResponseItem item in response.OutputItems)\n{\n    if (item is WebSearchCallResponseItem webSearchCall)\n    {\n        Console.WriteLine($\"[Web search invoked]({webSearchCall.Status}) {webSearchCall.Id}\");\n    }\n    else if (item is MessageResponseItem message)\n    {\n        Console.WriteLine($\"[{message.Role}] {message.Content?.FirstOrDefault()?.Text}\");\n    }\n}\n```\n\n## How to generate text embeddings\n\nIn this example, you want to create a trip-planning website that allows customers to write a prompt describing the kind of hotel that they are looking for and then offers hotel recommendations that closely match this description. To achieve this, it is possible to use text embeddings to measure the relatedness of text strings. In summary, you can get embeddings of the hotel descriptions, store them in a vector database, and use them to build a search index that you can query using the embedding of a given customer's prompt.\n\nTo generate a text embedding, use `EmbeddingClient` from the `OpenAI.Embeddings` namespace:\n\n```csharp\nusing OpenAI.Embeddings;\n\nEmbeddingClient client = new(\"text-embedding-3-small\", Environment.GetEnvironmentVariable(\"OPENAI_API_KEY\"));\n\nstring description = \"Best hotel in town if you like luxury hotels. They have an amazing infinity pool, a spa,\"\n    + \" and a really helpful concierge. The location is perfect -- right downtown, close to all the tourist\"\n    + \" attractions. We highly recommend this hotel.\";\n\nOpenAIEmbedding embedding = client.GenerateEmbedding(description);\nReadOnlyMemory\u003cfloat\u003e vector = embedding.ToFloats();\n```\n\nNotice that the resulting embedding is a list (also called a vector) of floating point numbers represented as an instance of `ReadOnlyMemory\u003cfloat\u003e`. By default, the length of the embedding vector will be 1536 when using the `text-embedding-3-small` model or 3072 when using the `text-embedding-3-large` model. Generally, larger embeddings perform better, but using them also tends to cost more in terms of compute, memory, and storage. You can reduce the dimensions of the embedding by creating an instance of the `EmbeddingGenerationOptions` class, setting the `Dimensions` property, and passing it as an argument in your call to the `GenerateEmbedding` method:\n\n```csharp\nEmbeddingGenerationOptions options = new() { Dimensions = 512 };\n\nOpenAIEmbedding embedding = client.GenerateEmbedding(description, options);\n```\n\n## How to generate images\n\nIn this example, you want to build an app to help interior designers prototype new ideas based on the latest design trends. As part of the creative process, an interior designer can use this app to generate images for inspiration simply by describing the scene in their head as a prompt. As expected, high-quality, strikingly dramatic images with finer details deliver the best results for this application.\n\nTo generate an image, use `ImageClient` from the `OpenAI.Images` namespace:\n\n```csharp\nusing OpenAI.Images;\n\nImageClient client = new(\"dall-e-3\", Environment.GetEnvironmentVariable(\"OPENAI_API_KEY\"));\n```\n\nGenerating an image always requires a `prompt` that describes what should be generated. To further tailor the image generation to your specific needs, you can create an instance of the `ImageGenerationOptions` class and set the `Quality`, `Size`, and `Style` properties accordingly. Note that you can also set the `ResponseFormat` property of `ImageGenerationOptions` to `GeneratedImageFormat.Bytes` in order to receive the resulting PNG as `BinaryData` (instead of the default remote `Uri`) if this is convenient for your use case.\n\n```csharp\nstring prompt = \"The concept for a living room that blends Scandinavian simplicity with Japanese minimalism for\"\n    + \" a serene and cozy atmosphere. It's a space that invites relaxation and mindfulness, with natural light\"\n    + \" and fresh air. Using neutral tones, including colors like white, beige, gray, and black, that create a\"\n    + \" sense of harmony. Featuring sleek wood furniture with clean lines and subtle curves to add warmth and\"\n    + \" elegance. Plants and flowers in ceramic pots adding color and life to a space. They can serve as focal\"\n    + \" points, creating a connection with nature. Soft textiles and cushions in organic fabrics adding comfort\"\n    + \" and softness to a space. They can serve as accents, adding contrast and texture.\";\n\nImageGenerationOptions options = new()\n{\n    Quality = GeneratedImageQuality.High,\n    Size = GeneratedImageSize.W1792xH1024,\n    Style = GeneratedImageStyle.Vivid,\n    ResponseFormat = GeneratedImageFormat.Bytes\n};\n```\n\nFinally, call the `ImageClient`'s `GenerateImage` method by passing the prompt and the `ImageGenerationOptions` instance as arguments:\n\n```csharp\nGeneratedImage image = client.GenerateImage(prompt, options);\nBinaryData bytes = image.ImageBytes;\n```\n\nFor illustrative purposes, you could then save the generated image to local storage:\n\n```csharp\nusing FileStream stream = File.OpenWrite($\"{Guid.NewGuid()}.png\");\nbytes.ToStream().CopyTo(stream);\n```\n\n## How to transcribe audio\n\nIn this example, an audio file is transcribed using the Whisper speech-to-text model, including both word- and audio-segment-level timestamp information.\n\n```csharp\nusing OpenAI.Audio;\n\nAudioClient client = new(\"whisper-1\", Environment.GetEnvironmentVariable(\"OPENAI_API_KEY\"));\n\nstring audioFilePath = Path.Combine(\"Assets\", \"audio_houseplant_care.mp3\");\n\nAudioTranscriptionOptions options = new()\n{\n    ResponseFormat = AudioTranscriptionFormat.Verbose,\n    TimestampGranularities = AudioTimestampGranularities.Word | AudioTimestampGranularities.Segment,\n};\n\nAudioTranscription transcription = client.TranscribeAudio(audioFilePath, options);\n\nConsole.WriteLine(\"Transcription:\");\nConsole.WriteLine($\"{transcription.Text}\");\n\nConsole.WriteLine();\nConsole.WriteLine($\"Words:\");\nforeach (TranscribedWord word in transcription.Words)\n{\n    Console.WriteLine($\"  {word.Word,15} : {word.StartTime.TotalMilliseconds,5:0} - {word.EndTime.TotalMilliseconds,5:0}\");\n}\n\nConsole.WriteLine();\nConsole.WriteLine($\"Segments:\");\nforeach (TranscribedSegment segment in transcription.Segments)\n{\n    Console.WriteLine($\"  {segment.Text,90} : {segment.StartTime.TotalMilliseconds,5:0} - {segment.EndTime.TotalMilliseconds,5:0}\");\n}\n```\n\n## How to use assistants with retrieval augmented generation (RAG)\n\nIn this example, you have a JSON document with the monthly sales information of different products, and you want to build an assistant capable of analyzing it and answering questions about it.\n\nTo achieve this, use both `OpenAIFileClient` from the `OpenAI.Files` namespace and `AssistantClient` from the `OpenAI.Assistants` namespace.\n\nImportant: The Assistants REST API is currently in beta. As such, the details are subject to change, and correspondingly the `AssistantClient` is attributed as `[Experimental]`. To use it, you must suppress the `OPENAI001` warning first.\n\n```csharp\nusing OpenAI.Assistants;\nusing OpenAI.Files;\n\nOpenAIClient openAIClient = new(Environment.GetEnvironmentVariable(\"OPENAI_API_KEY\"));\nOpenAIFileClient fileClient = openAIClient.GetOpenAIFileClient();\nAssistantClient assistantClient = openAIClient.GetAssistantClient();\n```\n\nHere is an example of what the JSON document might look like:\n\n```csharp\nusing Stream document = BinaryData.FromBytes(\"\"\"\n    {\n        \"description\": \"This document contains the sale history data for Contoso products.\",\n        \"sales\": [\n            {\n                \"month\": \"January\",\n                \"by_product\": {\n                    \"113043\": 15,\n                    \"113045\": 12,\n                    \"113049\": 2\n                }\n            },\n            {\n                \"month\": \"February\",\n                \"by_product\": {\n                    \"113045\": 22\n                }\n            },\n            {\n                \"month\": \"March\",\n                \"by_product\": {\n                    \"113045\": 16,\n                    \"113055\": 5\n                }\n            }\n        ]\n    }\n    \"\"\"u8.ToArray()).ToStream();\n```\n\nUpload this document to OpenAI using the `OpenAIFileClient`'s `UploadFile` method, ensuring that you use `FileUploadPurpose.Assistants` to allow your assistant to access it later:\n\n```csharp\nOpenAIFile salesFile = fileClient.UploadFile(\n    document,\n    \"monthly_sales.json\",\n    FileUploadPurpose.Assistants);\n```\n\nCreate a new assistant using an instance of the `AssistantCreationOptions` class to customize it. Here, we use:\n\n- A friendly `Name` for the assistant, as will display in the Playground\n- Tool definition instances for the tools that the assistant should have access to; here, we use `FileSearchToolDefinition` to process the sales document we just uploaded and `CodeInterpreterToolDefinition` so we can analyze and visualize the numeric data\n- Resources for the assistant to use with its tools, here using the `VectorStoreCreationHelper` type to automatically make a new vector store that indexes the sales file; alternatively, you could use `VectorStoreClient` to manage the vector store separately\n\n```csharp\nAssistantCreationOptions assistantOptions = new()\n{\n    Name = \"Example: Contoso sales RAG\",\n    Instructions =\n        \"You are an assistant that looks up sales data and helps visualize the information based\"\n        + \" on user queries. When asked to generate a graph, chart, or other visualization, use\"\n        + \" the code interpreter tool to do so.\",\n    Tools =\n    {\n        new FileSearchToolDefinition(),\n        new CodeInterpreterToolDefinition(),\n    },\n    ToolResources = new()\n    {\n        FileSearch = new()\n        {\n            NewVectorStores =\n            {\n                new VectorStoreCreationHelper([salesFile.Id]),\n            }\n        }\n    },\n};\n\nAssistant assistant = assistantClient.CreateAssistant(\"gpt-4o\", assistantOptions);\n```\n\nNext, create a new thread. For illustrative purposes, you could include an initial user message asking about the sales information of a given product and then use the `AssistantClient`'s `CreateThreadAndRun` method to get it started:\n\n```csharp\nThreadCreationOptions threadOptions = new()\n{\n    InitialMessages = { \"How well did product 113045 sell in February? Graph its trend over time.\" }\n};\n\nThreadRun threadRun = assistantClient.CreateThreadAndRun(assistant.Id, threadOptions);\n```\n\nPoll the status of the run until it is no longer queued or in progress:\n\n```csharp\ndo\n{\n    Thread.Sleep(TimeSpan.FromSeconds(1));\n    threadRun = assistantClient.GetRun(threadRun.ThreadId, threadRun.Id);\n} while (!threadRun.Status.IsTerminal);\n```\n\nIf everything went well, the terminal status of the run will be `RunStatus.Completed`.\n\nFinally, you can use the `AssistantClient`'s `GetMessages` method to retrieve the messages associated with this thread, which now include the responses from the assistant to the initial user message.\n\nFor illustrative purposes, you could print the messages to the console and also save any images produced by the assistant to local storage:\n\n```csharp\nCollectionResult\u003cThreadMessage\u003e messages\n    = assistantClient.GetMessages(threadRun.ThreadId, new MessageCollectionOptions() { Order = MessageCollectionOrder.Ascending });\n\nforeach (ThreadMessage message in messages)\n{\n    Console.Write($\"[{message.Role.ToString().ToUpper()}]: \");\n    foreach (MessageContent contentItem in message.Content)\n    {\n        if (!string.IsNullOrEmpty(contentItem.Text))\n        {\n            Console.WriteLine($\"{contentItem.Text}\");\n\n            if (contentItem.TextAnnotations.Count \u003e 0)\n            {\n                Console.WriteLine();\n            }\n\n            // Include annotations, if any.\n            foreach (TextAnnotation annotation in contentItem.TextAnnotations)\n            {\n                if (!string.IsNullOrEmpty(annotation.InputFileId))\n                {\n                    Console.WriteLine($\"* File citation, file ID: {annotation.InputFileId}\");\n                }\n                if (!string.IsNullOrEmpty(annotation.OutputFileId))\n                {\n                    Console.WriteLine($\"* File output, new file ID: {annotation.OutputFileId}\");\n                }\n            }\n        }\n        if (!string.IsNullOrEmpty(contentItem.ImageFileId))\n        {\n            OpenAIFile imageInfo = fileClient.GetFile(contentItem.ImageFileId);\n            BinaryData imageBytes = fileClient.DownloadFile(contentItem.ImageFileId);\n            using FileStream stream = File.OpenWrite($\"{imageInfo.Filename}.png\");\n            imageBytes.ToStream().CopyTo(stream);\n\n            Console.WriteLine($\"\u003cimage: {imageInfo.Filename}.png\u003e\");\n        }\n    }\n    Console.WriteLine();\n}\n```\n\nAnd it would yield something like this:\n\n```text\n[USER]: How well did product 113045 sell in February? Graph its trend over time.\n\n[ASSISTANT]: Product 113045 sold 22 units in February【4:0†monthly_sales.json】.\n\nNow, I will generate a graph to show its sales trend over time.\n\n* File citation, file ID: file-hGOiwGNftMgOsjbynBpMCPFn\n\n[ASSISTANT]: \u003cimage: 015d8e43-17fe-47de-af40-280f25452280.png\u003e\nThe sales trend for Product 113045 over the past three months shows that:\n\n- In January, 12 units were sold.\n- In February, 22 units were sold, indicating significant growth.\n- In March, sales dropped slightly to 16 units.\n\nThe graph above visualizes this trend, showing a peak in sales during February.\n```\n\n## How to use assistants with streaming and vision\n\nThis example shows how to use the v2 Assistants API to provide image data to an assistant and then stream the run's response.\n\nAs before, you will use a `OpenAIFileClient` and an `AssistantClient`:\n\n```csharp\nOpenAIClient openAIClient = new(Environment.GetEnvironmentVariable(\"OPENAI_API_KEY\"));\nOpenAIFileClient fileClient = openAIClient.GetOpenAIFileClient();\nAssistantClient assistantClient = openAIClient.GetAssistantClient();\n```\n\nFor this example, we will use both image data from a local file as well as an image located at a URL. For the local data, we upload the file with the `Vision` upload purpose, which would also allow it to be downloaded and retrieved later.\n\n```csharp\nOpenAIFile pictureOfAppleFile = fileClient.UploadFile(\n    Path.Combine(\"Assets\", \"images_apple.png\"),\n    FileUploadPurpose.Vision);\n\nUri linkToPictureOfOrange = new(\"https://raw.githubusercontent.com/openai/openai-dotnet/refs/heads/main/examples/Assets/images_orange.png\");\n```\n\nNext, create a new assistant with a vision-capable model like `gpt-4o` and a thread with the image information referenced:\n\n```csharp\nAssistant assistant = assistantClient.CreateAssistant(\n    \"gpt-4o\",\n    new AssistantCreationOptions()\n    {\n        Instructions = \"When asked a question, attempt to answer very concisely. \"\n            + \"Prefer one-sentence answers whenever feasible.\"\n    });\n\nAssistantThread thread = assistantClient.CreateThread(new ThreadCreationOptions()\n{\n    InitialMessages =\n        {\n            new ThreadInitializationMessage(\n                MessageRole.User,\n                [\n                    \"Hello, assistant! Please compare these two images for me:\",\n                    MessageContent.FromImageFileId(pictureOfAppleFile.Id),\n                    MessageContent.FromImageUri(linkToPictureOfOrange),\n                ]),\n        }\n});\n```\n\nWith the assistant and thread prepared, use the `CreateRunStreaming` method to get an enumerable `CollectionResult\u003cStreamingUpdate\u003e`. You can then iterate over this collection with `foreach`. For async calling patterns, use `CreateRunStreamingAsync` and iterate over the `AsyncCollectionResult\u003cStreamingUpdate\u003e` with `await foreach`, instead. Note that streaming variants also exist for `CreateThreadAndRunStreaming` and `SubmitToolOutputsToRunStreaming`.\n\n```csharp\nCollectionResult\u003cStreamingUpdate\u003e streamingUpdates = assistantClient.CreateRunStreaming(\n    thread.Id,\n    assistant.Id,\n    new RunCreationOptions()\n    {\n        AdditionalInstructions = \"When possible, try to sneak in puns if you're asked to compare things.\",\n    });\n```\n\nFinally, to handle the `StreamingUpdates` as they arrive, you can use the `UpdateKind` property on the base `StreamingUpdate` and/or downcast to a specifically desired update type, like `MessageContentUpdate` for `thread.message.delta` events or `RequiredActionUpdate` for streaming tool calls.\n\n```csharp\nforeach (StreamingUpdate streamingUpdate in streamingUpdates)\n{\n    if (streamingUpdate.UpdateKind == StreamingUpdateReason.RunCreated)\n    {\n        Console.WriteLine($\"--- Run started! ---\");\n    }\n    if (streamingUpdate is MessageContentUpdate contentUpdate)\n    {\n        Console.Write(contentUpdate.Text);\n    }\n}\n```\n\nThis will yield streamed output from the run like the following:\n\n```text\n--- Run started! ---\nThe first image depicts a multicolored apple with a blend of red and green hues, while the second image shows an orange with a bright, textured orange peel; one might say it’s comparing apples to oranges!\n```\n\n## How to work with Azure OpenAI\n\nFor Azure OpenAI scenarios use the [Azure SDK](https://github.com/Azure/azure-sdk-for-net) and more specifically the [Azure OpenAI client library for .NET](https://github.com/Azure/azure-sdk-for-net/blob/main/sdk/openai/Azure.AI.OpenAI/README.md). \n\nThe Azure OpenAI client library for .NET is a companion to this library and all common capabilities between OpenAI and Azure OpenAI share the same scenario clients, methods, and request/response types. It is designed to make Azure specific scenarios straightforward, with extensions for Azure-specific concepts like Responsible AI content filter results and On Your Data integration.\n\n```c#\nAzureOpenAIClient azureClient = new(\n    new Uri(\"https://your-azure-openai-resource.com\"),\n    new DefaultAzureCredential());\nChatClient chatClient = azureClient.GetChatClient(\"my-gpt-35-turbo-deployment\");\n\nChatCompletion completion = chatClient.CompleteChat(\n    [\n        // System messages represent instructions or other guidance about how the assistant should behave\n        new SystemChatMessage(\"You are a helpful assistant that talks like a pirate.\"),\n        // User messages represent user input, whether historical or the most recen tinput\n        new UserChatMessage(\"Hi, can you help me?\"),\n        // Assistant messages in a request represent conversation history for responses\n        new AssistantChatMessage(\"Arrr! Of course, me hearty! What can I do for ye?\"),\n        new UserChatMessage(\"What's the best way to train a parrot?\"),\n    ]);\n\nConsole.WriteLine($\"{completion.Role}: {completion.Content[0].Text}\");\n```\n\n## Advanced scenarios\n\n### Using protocol methods\n\nIn addition to the client methods that use strongly-typed request and response objects, the .NET library also provides _protocol methods_ that enable more direct access to the REST API. Protocol methods are \"binary in, binary out\" accepting `BinaryContent` as request bodies and providing `BinaryData` as response bodies.\n\nFor example, to use the protocol method variant of the `ChatClient`'s `CompleteChat` method, pass the request body as `BinaryContent`:\n\n```csharp\nChatClient client = new(\"gpt-4o\", Environment.GetEnvironmentVariable(\"OPENAI_API_KEY\"));\n\nBinaryData input = BinaryData.FromBytes(\"\"\"\n    {\n       \"model\": \"gpt-4o\",\n       \"messages\": [\n           {\n               \"role\": \"user\",\n               \"content\": \"Say 'this is a test.'\"\n           }\n       ]\n    }\n    \"\"\"u8.ToArray());\n\nusing BinaryContent content = BinaryContent.Create(input);\nClientResult result = client.CompleteChat(content);\nBinaryData output = result.GetRawResponse().Content;\n\nusing JsonDocument outputAsJson = JsonDocument.Parse(output.ToString());\nstring message = outputAsJson.RootElement\n    .GetProperty(\"choices\"u8)[0]\n    .GetProperty(\"message\"u8)\n    .GetProperty(\"content\"u8)\n    .GetString();\n\nConsole.WriteLine($\"[ASSISTANT]: {message}\");\n```\n\nNotice how you can then call the resulting `ClientResult`'s `GetRawResponse` method and retrieve the response body as `BinaryData` via the `PipelineResponse`'s `Content` property.\n\n### Mock a client for testing\n\nThe OpenAI .NET library has been designed to support mocking, providing key features such as:\n\n- Client methods made virtual to allow overriding.\n- Model factories to assist in instantiating API output models that lack public constructors.\n\nTo illustrate how mocking works, suppose you want to validate the behavior of the following method using the [Moq](https://github.com/devlooped/moq) library. Given the path to an audio file, it determines whether it contains a specified secret word:\n\n```csharp\npublic bool ContainsSecretWord(AudioClient client, string audioFilePath, string secretWord)\n{\n    AudioTranscription transcription = client.TranscribeAudio(audioFilePath);\n    return transcription.Text.Contains(secretWord);\n}\n```\n\nCreate mocks of `AudioClient` and `ClientResult\u003cAudioTranscription\u003e`, set up methods and properties that will be invoked, then test the behavior of the `ContainsSecretWord` method. Since the `AudioTranscription` class does not provide public constructors, it must be instantiated by the `OpenAIAudioModelFactory` static class:\n\n```csharp\n// Instantiate mocks and the AudioTranscription object.\n\nMock\u003cAudioClient\u003e mockClient = new();\nMock\u003cClientResult\u003cAudioTranscription\u003e\u003e mockResult = new(null, Mock.Of\u003cPipelineResponse\u003e());\nAudioTranscription transcription = OpenAIAudioModelFactory.AudioTranscription(text: \"I swear I saw an apple flying yesterday!\");\n\n// Set up mocks' properties and methods.\n\nmockResult\n    .SetupGet(result =\u003e result.Value)\n    .Returns(transcription);\n\nmockClient.Setup(client =\u003e client.TranscribeAudio(\n        It.IsAny\u003cstring\u003e(),\n        It.IsAny\u003cAudioTranscriptionOptions\u003e()))\n    .Returns(mockResult.Object);\n\n// Perform validation.\n\nAudioClient client = mockClient.Object;\nbool containsSecretWord = ContainsSecretWord(client, \"\u003caudioFilePath\u003e\", \"apple\");\n\nAssert.That(containsSecretWord, Is.True);\n```\n\nAll namespaces have their corresponding model factory to support mocking with the exception of the `OpenAI.Assistants` and `OpenAI.VectorStores` namespaces, for which model factories are coming soon.\n\n### Automatically retrying errors\n\nBy default, the client classes will automatically retry the following errors up to three additional times using exponential backoff:\n\n- 408 Request Timeout\n- 429 Too Many Requests\n- 500 Internal Server Error\n- 502 Bad Gateway\n- 503 Service Unavailable\n- 504 Gateway Timeout\n\n### Observability\n\nOpenAI .NET library supports experimental distributed tracing and metrics with OpenTelemetry. Check out [Observability with OpenTelemetry](./docs/observability.md) for more details.","funding_links":[],"categories":["**Section 2** : Azure OpenAI and Reference Architecture","Libraries \u0026 SDKs","C#","C\\#","OpenAI SDK","Openai","dotnet"],"sub_categories":["**Azure Reference Architectures**","Official SDKs","Teams"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fopenai%2Fopenai-dotnet","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fopenai%2Fopenai-dotnet","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fopenai%2Fopenai-dotnet/lists"}