{"id":13644086,"url":"https://github.com/rageagainstthepixel/openai-dotnet","last_synced_at":"2025-05-14T01:03:07.399Z","repository":{"id":41447491,"uuid":"363412356","full_name":"RageAgainstThePixel/OpenAI-DotNet","owner":"RageAgainstThePixel","description":"A Non-Official OpenAI RESTful API Client for DotNet","archived":false,"fork":false,"pushed_at":"2025-03-21T13:43:12.000Z","size":3541,"stargazers_count":735,"open_issues_count":6,"forks_count":152,"subscribers_count":17,"default_branch":"main","last_synced_at":"2025-04-05T22:01:48.577Z","etag":null,"topics":["ai","api-proxy","chat-gpt","chatgpt","csharp","dall-e","dall-e-3","dotnet","dotnet-core","dotnet-standard","dotnetcore","gpt","gpt-4","gpt-4o","gpt-4o-mini","gpt-realtime","gpt-turbo","machine-learning","openai","openai-api"],"latest_commit_sha":null,"homepage":"https://openai.com","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/RageAgainstThePixel.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","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},"funding":{"github":"StephenHodgson","patreon":"RageAgainstThePixel"}},"created_at":"2021-05-01T13:07:26.000Z","updated_at":"2025-04-03T00:56:44.000Z","dependencies_parsed_at":"2023-11-12T17:50:36.493Z","dependency_job_id":"1f563efe-2f91-4b9d-8233-e50e8b33f63e","html_url":"https://github.com/RageAgainstThePixel/OpenAI-DotNet","commit_stats":{"total_commits":137,"total_committers":10,"mean_commits":13.7,"dds":0.416058394160584,"last_synced_commit":"b1aba5d1e8d35c698777519deedb4f9642a92dcd"},"previous_names":[],"tags_count":119,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RageAgainstThePixel%2FOpenAI-DotNet","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RageAgainstThePixel%2FOpenAI-DotNet/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RageAgainstThePixel%2FOpenAI-DotNet/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RageAgainstThePixel%2FOpenAI-DotNet/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/RageAgainstThePixel","download_url":"https://codeload.github.com/RageAgainstThePixel/OpenAI-DotNet/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248646201,"owners_count":21138968,"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":["ai","api-proxy","chat-gpt","chatgpt","csharp","dall-e","dall-e-3","dotnet","dotnet-core","dotnet-standard","dotnetcore","gpt","gpt-4","gpt-4o","gpt-4o-mini","gpt-realtime","gpt-turbo","machine-learning","openai","openai-api"],"created_at":"2024-08-02T01:01:57.371Z","updated_at":"2025-04-12T23:27:40.394Z","avatar_url":"https://github.com/RageAgainstThePixel.png","language":"C#","funding_links":["https://github.com/sponsors/StephenHodgson","https://patreon.com/RageAgainstThePixel"],"categories":["CLIs"],"sub_categories":[],"readme":"# OpenAI-DotNet\n\n[![Discord](https://img.shields.io/discord/855294214065487932.svg?label=\u0026logo=discord\u0026logoColor=ffffff\u0026color=7389D8\u0026labelColor=6A7EC2)](https://discord.gg/xQgMW9ufN4)\n[![NuGet version (OpenAI-DotNet)](https://img.shields.io/nuget/v/OpenAI-DotNet.svg?label=OpenAI-DotNet\u0026logo=nuget)](https://www.nuget.org/packages/OpenAI-DotNet/)\n[![NuGet Downloads](https://img.shields.io/nuget/dt/OpenAI-DotNet)](https://www.nuget.org/packages/OpenAI-DotNet/)\n[![NuGet version (OpenAI-DotNet-Proxy)](https://img.shields.io/nuget/v/OpenAI-DotNet-Proxy.svg?label=OpenAI-DotNet-Proxy\u0026logo=nuget)](https://www.nuget.org/packages/OpenAI-DotNet-Proxy/)\n[![Nuget Publish](https://github.com/RageAgainstThePixel/OpenAI-DotNet/actions/workflows/Publish-Nuget.yml/badge.svg)](https://github.com/RageAgainstThePixel/OpenAI-DotNet/actions/workflows/Publish-Nuget.yml)\n\nA simple C# .NET client library for [OpenAI](https://openai.com/) to use though their RESTful API.\nIndependently developed, this is not an official library and I am not affiliated with OpenAI.\nAn OpenAI API account is required.\n\nOriginally Forked from [OpenAI-API-dotnet](https://github.com/OkGoDoIt/OpenAI-API-dotnet).\nMore context [on Roger Pincombe's blog](https://rogerpincombe.com/openai-dotnet-api).\n\n## Requirements\n\n- This library targets .NET 8.0 and above.\n- It should work across console apps, winforms, wpf, asp.net, etc.\n- It should also work across Windows, Linux, and Mac.\n\n## Getting started\n\n### Install from NuGet\n\nInstall package [`OpenAI-DotNet` from Nuget](https://www.nuget.org/packages/OpenAI-DotNet/).  Here's how via command line:\n\npowershell:\n\n```terminal\nInstall-Package OpenAI-DotNet\n```\n\ndotnet:\n\n```terminal\ndotnet add package OpenAI-DotNet\n```\n\n\u003e Looking to [use OpenAI-DotNet in the Unity Game Engine](https://github.com/RageAgainstThePixel/com.openai.unity)? Check out our unity package on OpenUPM:\n\u003e\n\u003e[![openupm](https://img.shields.io/npm/v/com.openai.unity?label=openupm\u0026registry_uri=https://package.openupm.com)](https://openupm.com/packages/com.openai.unity/)\n\n## [Documentation](https://rageagainstthepixel.github.io/OpenAI-DotNet)\n\n\u003e Check out our new api docs!\n\n\u003chttps://rageagainstthepixel.github.io/OpenAI-DotNet\u003e\n\n### Table of Contents\n\n- [Authentication](#authentication)\n- [Azure OpenAI](#azure-openai)\n  - [Azure Active Directory Authentication](#azure-active-directory-authentication)\n- [OpenAI API Proxy](#openai-api-proxy)\n- [Models](#models)\n  - [List Models](#list-models)\n  - [Retrieve Models](#retrieve-model)\n  - [Delete Fine Tuned Model](#delete-fine-tuned-model)\n- [Realtime](#realtime)\n  - [Create Realtime Session](#create-realtime-session)\n  - [Client Events](#client-events)\n    - [Sending Client Events](#sending-client-events)\n  - [Server Events](#server-events)\n    - [Receiving Server Events](#receiving-server-events)\n- [Assistants](#assistants)\n  - [List Assistants](#list-assistants)\n  - [Create Assistant](#create-assistant)\n  - [Retrieve Assistant](#retrieve-assistant)\n  - [Modify Assistant](#modify-assistant)\n  - [Delete Assistant](#delete-assistant)\n  - [Assistant Streaming](#assistant-streaming)\n  - [Threads](#threads)\n    - [Create Thread](#create-thread)\n    - [Create Thread and Run](#create-thread-and-run)\n      - [Streaming](#create-thread-and-run-streaming)\n    - [Retrieve Thread](#retrieve-thread)\n    - [Modify Thread](#modify-thread)\n    - [Delete Thread](#delete-thread)\n    - [Thread Messages](#thread-messages)\n      - [List Messages](#list-thread-messages)\n      - [Create Message](#create-thread-message)\n      - [Retrieve Message](#retrieve-thread-message)\n      - [Modify Message](#modify-thread-message)\n    - [Thread Runs](#thread-runs)\n      - [List Runs](#list-thread-runs)\n      - [Create Run](#create-thread-run)\n        - [Streaming](#create-thread-run-streaming)\n      - [Retrieve Run](#retrieve-thread-run)\n      - [Modify Run](#modify-thread-run)\n      - [Submit Tool Outputs to Run](#thread-submit-tool-outputs-to-run)\n      - [Structured Outputs](#thread-structured-outputs)\n      - [List Run Steps](#list-thread-run-steps)\n      - [Retrieve Run Step](#retrieve-thread-run-step)\n      - [Cancel Run](#cancel-thread-run)\n  - [Vector Stores](#vector-stores)\n    - [List Vector Stores](#list-vector-stores)\n    - [Create Vector Store](#create-vector-store)\n    - [Retrieve Vector Store](#retrieve-vector-store)\n    - [Modify Vector Store](#modify-vector-store)\n    - [Delete Vector Store](#delete-vector-store)\n    - [Vector Store Files](#vector-store-files)\n      - [List Vector Store Files](#list-vector-store-files)\n      - [Create Vector Store File](#create-vector-store-file)\n      - [Retrieve Vector Store File](#retrieve-vector-store-file)\n      - [Delete Vector Store File](#delete-vector-store-file)\n    - [Vector Store File Batches](#vector-store-file-batches)\n      - [Create Vector Store File Batch](#create-vector-store-file-batch)\n      - [Retrieve Vector Store File Batch](#retrieve-vector-store-file-batch)\n      - [List Files In Vector Store Batch](#list-files-in-vector-store-batch)\n      - [Cancel Vector Store File Batch](#cancel-vector-store-file-batch)\n- [Chat](#chat)\n  - [Chat Completions](#chat-completions)\n  - [Streaming](#chat-streaming)\n  - [Tools](#chat-tools)\n  - [Vision](#chat-vision)\n  - [Audio](#chat-audio)\n  - [Structured Outputs](#chat-structured-outputs)\n  - [Json Mode](#chat-json-mode)\n- [Audio](#audio)\n  - [Create Speech](#create-speech)\n  - [Create Transcription](#create-transcription)\n  - [Create Translation](#create-translation)\n- [Images](#images)\n  - [Create Image](#create-image)\n  - [Edit Image](#edit-image)\n  - [Create Image Variation](#create-image-variation)\n- [Files](#files)\n  - [List Files](#list-files)\n  - [Upload File](#upload-file)\n  - [Delete File](#delete-file)\n  - [Retrieve File](#retrieve-file-info)\n  - [Download File Content](#download-file-content)\n- [Fine Tuning](#fine-tuning)\n  - [Create Fine Tune Job](#create-fine-tune-job)\n  - [List Fine Tune Jobs](#list-fine-tune-jobs)\n  - [Retrieve Fine Tune Job Info](#retrieve-fine-tune-job-info)\n  - [Cancel Fine Tune Job](#cancel-fine-tune-job)\n  - [List Fine Tune Job Events](#list-fine-tune-job-events)\n- [Batches](#batches)\n  - [List Batches](#list-batches)\n  - [Create Batch](#create-batch)\n  - [Retrieve Batch](#retrieve-batch)\n  - [Cancel Batch](#cancel-batch)\n- [Embeddings](#embeddings)\n  - [Create Embedding](#create-embeddings)\n- [Moderations](#moderations)\n  - [Create Moderation](#create-moderation)\n\n### [Authentication](https://platform.openai.com/docs/api-reference/authentication)\n\nThere are 3 ways to provide your API keys, in order of precedence:\n\n\u003e [!WARNING]\n\u003e We recommended using the environment variables to load the API key instead of having it hard coded in your source. It is not recommended use this method in production, but only for accepting user credentials, local testing and quick start scenarios.\n\n1. [Pass keys directly with constructor](#pass-keys-directly-with-constructor) :warning:\n2. [Load key from configuration file](#load-key-from-configuration-file)\n3. [Use System Environment Variables](#use-system-environment-variables)\n\n#### Pass keys directly with constructor\n\n\u003e [!WARNING]\n\u003e We recommended using the environment variables to load the API key instead of having it hard coded in your source. It is not recommended use this method in production, but only for accepting user credentials, local testing and quick start scenarios.\n\n```csharp\nusing var api = new OpenAIClient(\"sk-apiKey\");\n```\n\nOr create a `OpenAIAuthentication` object manually\n\n```csharp\nusing var api = new OpenAIClient(new OpenAIAuthentication(\"sk-apiKey\", \"org-yourOrganizationId\", \"proj_yourProjectId\"));\n```\n\n#### Load key from configuration file\n\nAttempts to load api keys from a configuration file, by default `.openai` in the current directory, optionally traversing up the directory tree or in the user's home directory.\n\nTo create a configuration file, create a new text file named `.openai` and containing the line:\n\n\u003e [!NOTE]\n\u003e Organization and project id entries are optional.\n\n##### Json format\n\n```json\n{\n  \"apiKey\": \"sk-aaaabbbbbccccddddd\",\n  \"organizationId\": \"org-yourOrganizationId\",\n  \"projectId\": \"proj_yourProjectId\"\n}\n```\n\n##### Deprecated format\n\n```shell\nOPENAI_API_KEY=sk-aaaabbbbbccccddddd\nOPENAI_ORGANIZATION_ID=org-yourOrganizationId\nOPENAI_PROJECT_ID=proj_yourProjectId\n```\n\nYou can also load the configuration file directly with known path by calling static methods in `OpenAIAuthentication`:\n\n- Loads the default `.openai` config in the specified directory:\n\n```csharp\nusing var api = new OpenAIClient(OpenAIAuthentication.LoadFromDirectory(\"path/to/your/directory\"));\n```\n\n- Loads the configuration file from a specific path. File does not need to be named `.openai` as long as it conforms to the json format:\n\n```csharp\nusing var api = new OpenAIClient(OpenAIAuthentication.LoadFromPath(\"path/to/your/file.json\"));\n```\n\n#### Use System Environment Variables\n\nUse your system's environment variables specify an api key and organization to use.\n\n- Use `OPENAI_API_KEY` for your api key.\n- Use `OPENAI_ORGANIZATION_ID` to specify an organization.\n- Use `OPENAI_PROJECT_ID` to specify a project.\n\n```csharp\nusing var api = new OpenAIClient(OpenAIAuthentication.LoadFromEnv());\n```\n\n### Handling OpenAIClient and HttpClient Lifecycle\n\n`OpenAIClient` implements `IDisposable` to manage the lifecycle of the resources it uses, including `HttpClient`. When you initialize `OpenAIClient`, it will create an internal `HttpClient` instance if one is not provided. This internal `HttpClient` is disposed of when `OpenAIClient` is disposed of. If you provide an external `HttpClient` instance to `OpenAIClient`, you are responsible for managing its disposal.\n\n- If `OpenAIClient` creates its own `HttpClient`, it will also take care of disposing it when you dispose `OpenAIClient`.\n- If an external `HttpClient` is passed to `OpenAIClient`, it will not be disposed of by `OpenAIClient`. You must manage the disposal of the `HttpClient` yourself.\n\nPlease ensure to appropriately dispose of `OpenAIClient` to release resources timely and to prevent any potential memory or resource leaks in your application.\n\nTypical usage with an internal `HttpClient`:\n\n```csharp\nusing var api = new OpenAIClient();\n```\n\nCustom `HttpClient` (which you must dispose of yourself):\n\n```csharp\nusing var customHttpClient = new HttpClient();\n// set custom http client properties here\nvar api = new OpenAIClient(client: customHttpClient);\n```\n\n### [Azure OpenAI](https://learn.microsoft.com/en-us/azure/cognitive-services/openai)\n\nYou can also choose to use Microsoft's Azure OpenAI deployments as well.\n\nYou can find the required information in the Azure Playground by clicking the `View Code` button and view a URL like this:\n\n```markdown\nhttps://{your-resource-name}.openai.azure.com/openai/deployments/{deployment-id}/chat/completions?api-version={api-version}\n```\n\n- `your-resource-name` The name of your Azure OpenAI Resource.\n- `deployment-id` The deployment name you chose when you deployed the model.\n- `api-version` The API version to use for this operation. This follows the YYYY-MM-DD format.\n\nTo setup the client to use your deployment, you'll need to pass in `OpenAIClientSettings` into the client constructor.\n\n```csharp\nvar auth = new OpenAIAuthentication(\"sk-apiKey\");\nvar settings = new OpenAIClientSettings(resourceName: \"your-resource-name\", deploymentId: \"deployment-id\", apiVersion: \"api-version\");\nusing var api = new OpenAIClient(auth, settings);\n```\n\n#### [Azure Active Directory Authentication](https://learn.microsoft.com/en-us/azure/cognitive-services/openai/reference#authentication)\n\n[Authenticate with MSAL](https://github.com/AzureAD/microsoft-authentication-library-for-dotnet) as usual and get access token, then use the access token when creating your `OpenAIAuthentication`. Then be sure to set useAzureActiveDirectory to true when creating your `OpenAIClientSettings`.\n\n[Tutorial: Desktop app that calls web APIs: Acquire a token](https://learn.microsoft.com/en-us/azure/active-directory/develop/scenario-desktop-acquire-token?tabs=dotnet)\n\n```csharp\n// get your access token using any of the MSAL methods\nvar accessToken = result.AccessToken;\nvar auth = new OpenAIAuthentication(accessToken);\nvar settings = new OpenAIClientSettings(resourceName: \"your-resource\", deploymentId: \"deployment-id\", apiVersion: \"api-version\", useActiveDirectoryAuthentication: true);\nusing var api = new OpenAIClient(auth, settings);\n```\n\n### [OpenAI API Proxy](OpenAI-DotNet-Proxy/Readme.md)\n\n[![NuGet version (OpenAI-DotNet-Proxy)](https://img.shields.io/nuget/v/OpenAI-DotNet-Proxy.svg?label=OpenAI-DotNet-Proxy\u0026logo=nuget)](https://www.nuget.org/packages/OpenAI-DotNet-Proxy/)\n\nUsing either the [OpenAI-DotNet](https://github.com/RageAgainstThePixel/OpenAI-DotNet) or [com.openai.unity](https://github.com/RageAgainstThePixel/com.openai.unity) packages directly in your front-end app may expose your API keys and other sensitive information. To mitigate this risk, it is recommended to set up an intermediate API that makes requests to OpenAI on behalf of your front-end app. This library can be utilized for both front-end and intermediary host configurations, ensuring secure communication with the OpenAI API.\n\n#### Front End Example\n\nIn the front end example, you will need to securely authenticate your users using your preferred OAuth provider. Once the user is authenticated, exchange your custom auth token with your API key on the backend.\n\nFollow these steps:\n\n1. Setup a new project using either the [OpenAI-DotNet](https://github.com/RageAgainstThePixel/OpenAI-DotNet) or [com.openai.unity](https://github.com/RageAgainstThePixel/com.openai.unity) packages.\n2. Authenticate users with your OAuth provider.\n3. After successful authentication, create a new `OpenAIAuthentication` object and pass in the custom token with the prefix `sess-`.\n4. Create a new `OpenAIClientSettings` object and specify the domain where your intermediate API is located.\n5. Pass your new `auth` and `settings` objects to the `OpenAIClient` constructor when you create the client instance.\n\nHere's an example of how to set up the front end:\n\n```csharp\nvar authToken = await LoginAsync();\nvar auth = new OpenAIAuthentication($\"sess-{authToken}\");\nvar settings = new OpenAIClientSettings(domain: \"api.your-custom-domain.com\");\nusing var api = new OpenAIClient(auth, settings);\n```\n\nThis setup allows your front end application to securely communicate with your backend that will be using the OpenAI-DotNet-Proxy, which then forwards requests to the OpenAI API. This ensures that your OpenAI API keys and other sensitive information remain secure throughout the process.\n\n#### Back End Example\n\nIn this example, we demonstrate how to set up and use `OpenAIProxy` in a new ASP.NET Core web app. The proxy server will handle authentication and forward requests to the OpenAI API, ensuring that your API keys and other sensitive information remain secure.\n\n1. Create a new [ASP.NET Core minimal web API](https://learn.microsoft.com/en-us/aspnet/core/tutorials/min-web-api?view=aspnetcore-6.0) project.\n2. Add the OpenAI-DotNet nuget package to your project.\n    - Powershell install: `Install-Package OpenAI-DotNet-Proxy`\n    - Dotnet install: `dotnet add package OpenAI-DotNet-Proxy`\n    - Manually editing .csproj: `\u003cPackageReference Include=\"OpenAI-DotNet-Proxy\" /\u003e`\n3. Create a new class that inherits from `AbstractAuthenticationFilter` and override the `ValidateAuthentication` method. This will implement the `IAuthenticationFilter` that you will use to check user session token against your internal server.\n4. In `Program.cs`, create a new proxy web application by calling `OpenAIProxy.CreateWebApplication` method, passing your custom `AuthenticationFilter` as a type argument.\n5. Create `OpenAIAuthentication` and `OpenAIClientSettings` as you would normally with your API keys, org id, or Azure settings.\n\n```csharp\npublic partial class Program\n{\n    private class AuthenticationFilter : AbstractAuthenticationFilter\n    {\n        public override async Task ValidateAuthenticationAsync(IHeaderDictionary request)\n        {\n            await Task.CompletedTask; // remote resource call to verify token\n\n            // You will need to implement your own class to properly test\n            // custom issued tokens you've setup for your end users.\n            if (!request.Authorization.ToString().Contains(TestUserToken))\n            {\n                throw new AuthenticationException(\"User is not authorized\");\n            }\n        }\n    }\n\n    public static void Main(string[] args)\n    {\n        var auth = OpenAIAuthentication.LoadFromEnv();\n        var settings = new OpenAIClientSettings(/* your custom settings if using Azure OpenAI */);\n        using var openAIClient = new OpenAIClient(auth, settings);\n        OpenAIProxy.CreateWebApplication\u003cAuthenticationFilter\u003e(args, openAIClient).Run();\n    }\n}\n```\n\nOnce you have set up your proxy server, your end users can now make authenticated requests to your proxy api instead of directly to the OpenAI API. The proxy server will handle authentication and forward requests to the OpenAI API, ensuring that your API keys and other sensitive information remain secure.\n\n---\n\n### [Models](https://platform.openai.com/docs/api-reference/models)\n\nList and describe the various models available in the API. You can refer to the [Models documentation](https://platform.openai.com/docs/models) to understand what models are available and the differences between them.\n\nAlso checkout [model endpoint compatibility](https://platform.openai.com/docs/models/model-endpoint-compatibility) to understand which models work with which endpoints.\n\nTo specify a custom model not pre-defined in this library:\n\n```csharp\nvar model = new Model(\"model-id\");\n```\n\nThe Models API is accessed via `OpenAIClient.ModelsEndpoint`\n\n#### [List models](https://platform.openai.com/docs/api-reference/models/list)\n\nLists the currently available models, and provides basic information about each one such as the owner and availability.\n\n```csharp\nusing var api = new OpenAIClient();\nvar models = await api.ModelsEndpoint.GetModelsAsync();\n\nforeach (var model in models)\n{\n    Console.WriteLine(model.ToString());\n}\n```\n\n#### [Retrieve model](https://platform.openai.com/docs/api-reference/models/retrieve)\n\nRetrieves a model instance, providing basic information about the model such as the owner and permissions.\n\n```csharp\nusing var api = new OpenAIClient();\nvar model = await api.ModelsEndpoint.GetModelDetailsAsync(\"gpt-4o\");\nConsole.WriteLine(model.ToString());\n```\n\n#### [Delete Fine Tuned Model](https://platform.openai.com/docs/api-reference/fine-tunes/delete-model)\n\nDelete a fine-tuned model. You must have the Owner role in your organization.\n\n```csharp\nusing var api = new OpenAIClient();\nvar isDeleted = await api.ModelsEndpoint.DeleteFineTuneModelAsync(\"your-fine-tuned-model\");\nAssert.IsTrue(isDeleted);\n```\n\n---\n\n### [Realtime](https://platform.openai.com/docs/api-reference/realtime)\n\n\u003e [!WARNING]\n\u003e Beta Feature. API subject to breaking changes.\n\n- [Realtime Guide](https://platform.openai.com/docs/guides/realtime)\n\nThe Realtime API enables you to build low-latency, multi-modal conversational experiences. It currently supports text and audio as both input and output, as well as function calling.\n\nThe Assistants API is accessed via `OpenAIClient.RealtimeEndpoint`\n\n#### Create Realtime Session\n\nHere is a simple example of how to create a realtime session and to send and receive messages from the model.\n\n```csharp\nusing var api = new OpenAIClient();\nvar cancellationTokenSource = new CancellationTokenSource();\nvar tools = new List\u003cTool\u003e\n{\n    Tool.FromFunc(\"goodbye\", () =\u003e\n    {\n        cancellationTokenSource.Cancel();\n        return \"Goodbye!\";\n    })\n};\nvar configuration = new SessionConfiguration(Model.GPT4oRealtime, tools: tools);\nusing var session = await api.RealtimeEndpoint.CreateSessionAsync(configuration);\nvar responseTask = session.ReceiveUpdatesAsync\u003cIServerEvent\u003e(ServerEvents, cancellationTokenSource.Token);\nawait session.SendAsync(new ConversationItemCreateRequest(\"Hello!\"));\nawait session.SendAsync(new CreateResponseRequest());\nawait session.SendAsync(new InputAudioBufferAppendRequest(new ReadOnlyMemory\u003cbyte\u003e(new byte[1024 * 4])), cancellationTokenSource.Token);\nawait session.SendAsync(new ConversationItemCreateRequest(\"GoodBye!\"));\nawait session.SendAsync(new CreateResponseRequest());\nawait responseTask;\n\nvoid ServerEvents(IServerEvent @event)\n{\n    switch (@event)\n    {\n        case ResponseAudioTranscriptResponse transcriptResponse:\n            Console.WriteLine(transcriptResponse.ToString());\n            break;\n        case ResponseFunctionCallArgumentsResponse functionCallResponse:\n            if (functionCallResponse.IsDone)\n            {\n                ToolCall toolCall = functionCallResponse;\n                toolCall.InvokeFunction();\n            }\n\n            break;\n    }\n}\n```\n\n#### Client Events\n\nThe library implements `IClientEvent` interface for outgoing client sent events.\n\n- [`UpdateSessionRequest`](https://platform.openai.com/docs/api-reference/realtime-client-events/session/update): Update the session with new session options.\n- [`InputAudioBufferAppendRequest`](https://platform.openai.com/docs/api-reference/realtime-client-events/input-audio-buffer/append): Append audio to the input audio buffer. (Unlike made other client events, the server will not send a confirmation response to this event).\n- [`InputAudioBufferCommitRequest`](https://platform.openai.com/docs/api-reference/realtime-client-events/input-audio-buffer/commit): Commit the input audio buffer. (When in Server VAD mode, the client does not need to send this event).\n- [`InputAudioBufferClearRequest`](https://platform.openai.com/docs/api-reference/realtime-client-events/input-audio-buffer/clear): Clear the input audio buffer.\n- [`ConversationItemCreateRequest`](https://platform.openai.com/docs/api-reference/realtime-client-events/conversation/item/create): Create a new conversation item. This is the main way to send user content to the model.\n- [`ConversationItemTruncateRequest`](https://platform.openai.com/docs/api-reference/realtime-client-events/conversation/item/truncate): Send this event to truncate a previous assistant message’s audio.\n- [`ConversationItemDeleteRequest`](https://platform.openai.com/docs/api-reference/realtime-client-events/conversation/item/delete): Delete a conversation item. This is useful when you want to remove a message from the conversation history.\n- [`CreateResponseRequest`](https://platform.openai.com/docs/api-reference/realtime-client-events/response/create): Create a response from the model. Send this event after creating new conversation items or invoking tool calls. This will trigger the model to generate a response.\n- [`ResponseCancelRequest`](https://platform.openai.com/docs/api-reference/realtime-client-events/response/cancel) -Send this event to cancel an in-progress response.\n\n##### Sending Client Events\n\nYou can send client events at any time to the server by calling the `RealtimeSession.SendAsync` method on the session object. The send call will return a `IServerEvent` handle that best represents the appropriate response from the server for that event. This is useful if you want to handle server responses in a more granular way.\n\nIdeally though, you may want to handle all server responses with [`RealtimeSession.ReceiveUpdatesAsync`](#receiving-server-events).\n\n\u003e [!NOTE]\n\u003e The server will not send a confirmation response to the `InputAudioBufferAppendRequest` event.\n\n\u003e [!IMPORTANT]\n\u003e You will also need to send `CreateResponseRequest` to trigger the model to generate a response.\n\n```csharp\nvar serverEvent = await session.SendAsync(new ConversationItemCreateRequest(\"Hello!\"));\nConsole.WriteLine(serverEvent.ToJsonString());\nserverEvent = await session.SendAsync(new CreateResponseRequest());\nConsole.WriteLine(serverEvent.ToJsonString());\n```\n\n#### Server Events\n\nThe library implements `IServerEvent` interface for incoming server sent events.\n\n- [`RealtimeEventError`](https://platform.openai.com/docs/api-reference/realtime-server-events/error): Returned when an error occurs, which could be a client problem or a server problem.\n- [`SessionResponse`](https://platform.openai.com/docs/api-reference/realtime-server-events/session): Returned for both a `session.created` and `session.updated` event.\n- [`RealtimeConversationResponse`](https://platform.openai.com/docs/api-reference/realtime-server-events/conversation/created): Returned when a new conversation item is created.\n- [`ConversationItemCreatedResponse`](https://platform.openai.com/docs/api-reference/realtime-server-events/conversation/item/created): Returned when a new conversation item is created.\n- [`ConversationItemInputAudioTranscriptionResponse`](https://platform.openai.com/docs/api-reference/realtime-server-events/conversation): Returned when the input audio transcription is completed or failed.\n- [`ConversationItemTruncatedResponse`](https://platform.openai.com/docs/api-reference/realtime-server-events/conversation/item/truncated): Returned when a conversation item is truncated.\n- [`ConversationItemDeletedResponse`](https://platform.openai.com/docs/api-reference/realtime-server-events/conversation/item/deleted): Returned when a conversation item is deleted.\n- [`InputAudioBufferCommittedResponse`](https://platform.openai.com/docs/api-reference/realtime-server-events/input_audio_buffer/committed): Returned when an input audio buffer is committed, either by the client or automatically in server VAD mode.\n- [`InputAudioBufferClearedResponse`](https://platform.openai.com/docs/api-reference/realtime-server-events/input_audio_buffer/cleared): Returned when an input audio buffer is cleared.\n- [`InputAudioBufferStartedResponse`](https://platform.openai.com/docs/api-reference/realtime-server-events/input_audio_buffer/speech_started): Sent by the server when in server_vad mode to indicate that speech has been detected in the audio buffer. This can happen any time audio is added to the buffer (unless speech is already detected). The client may want to use this event to interrupt audio playback or provide visual feedback to the user.\n- [`InputAudioBufferStoppedResponse`](https://platform.openai.com/docs/api-reference/realtime-server-events/input_audio_buffer/speech_stopped): Returned in server_vad mode when the server detects the end of speech in the audio buffer.\n- [`RealtimeResponse`](https://platform.openai.com/docs/api-reference/realtime-server-events/response): Returned when a response is created or done.\n- [`ResponseOutputItemResponse`](https://platform.openai.com/docs/api-reference/realtime-server-events/response/output_item): Returned when a response output item is added or done.\n- [`ResponseContentPartResponse`](https://platform.openai.com/docs/api-reference/realtime-server-events/response/content_part): Returned when a response content part is added or done.\n- [`ResponseTextResponse`](https://platform.openai.com/docs/api-reference/realtime-server-events/response/text): Returned when a response text is updated or done.\n- [`ResponseAudioTranscriptResponse`](https://platform.openai.com/docs/api-reference/realtime-server-events/response/audio_transcript): Returned when a response audio transcript is updated or done.\n- [`ResponseAudioResponse`](https://platform.openai.com/docs/api-reference/realtime-server-events/response/audio): Returned when a response audio is updated or done.\n- [`ResponseFunctionCallArgumentsResponse`](https://platform.openai.com/docs/api-reference/realtime-server-events/response/function_call_arguments): Returned when a response function call arguments are updated or done.\n- [`RateLimitsResponse`](https://platform.openai.com/docs/api-reference/realtime-server-events/rate_limits): Returned when rate limits are updated.\n\n##### Receiving Server Events\n\nTo receive server events, you will need to call the `RealtimeSession.ReceiveUpdatesAsync` method on the session object. This method will return a `Task` or `IAsyncEnumerable\u003cT\u003e` that will complete when the session is closed or when the cancellation token is triggered. Ideally this method should be called once and runs for the duration of the session.\n\n\u003e [!NOTE]\n\u003e You can also get sent `IClientEvent` callbacks as well by using the `IRealtimeEvent` interface instead of `IServerEvent`.\n\n```csharp\nawait foreach (var @event in session.ReceiveUpdatesAsync\u003cIServerEvent\u003e(cts.Token))\n{\n    switch (@event)\n    {\n        case RealtimeEventError error:\n            // raised anytime an error occurs\n            break;\n        case SessionResponse sessionResponse:\n            // raised when a session is created or updated\n            break;\n        case RealtimeConversationResponse conversationResponse:\n            // raised when a new conversation is created\n            break;\n        case ConversationItemCreatedResponse conversationItemCreated:\n            // raised when a new conversation item is created\n            break;\n        case ConversationItemInputAudioTranscriptionResponse conversationItemTranscription:\n            // raised when the input audio transcription is completed or failed\n            break;\n        case ConversationItemTruncatedResponse conversationItemTruncated:\n            // raised when a conversation item is truncated\n            break;\n        case ConversationItemDeletedResponse conversationItemDeleted:\n            // raised when a conversation item is deleted\n            break;\n        case InputAudioBufferCommittedResponse committedResponse:\n            // raised when an input audio buffer is committed\n            break;\n        case InputAudioBufferClearedResponse clearedResponse:\n            // raised when an input audio buffer is cleared\n            break;\n        case InputAudioBufferStartedResponse startedResponse:\n            // raised when speech is detected in the audio buffer\n            break;\n        case InputAudioBufferStoppedResponse stoppedResponse:\n            // raised when speech stops in the audio buffer\n            break;\n        case RealtimeResponse realtimeResponse:\n            // raised when a response is created or done\n            break;\n        case ResponseOutputItemResponse outputItemResponse:\n            // raised when a response output item is added or done\n            break;\n        case ResponseContentPartResponse contentPartResponse:\n            // raised when a response content part is added or done\n            break;\n        case ResponseTextResponse textResponse:\n            // raised when a response text is updated or done\n            break;\n        case ResponseAudioTranscriptResponse transcriptResponse:\n            // raised when a response audio transcript is updated or done\n            break;\n        case ResponseFunctionCallArgumentsResponse functionCallResponse:\n            // raised when a response function call arguments are updated or done\n            break;\n        case RateLimitsResponse rateLimitsResponse:\n            // raised when rate limits are updated\n            break;\n    }\n}\n```\n\n---\n\n### [Assistants](https://platform.openai.com/docs/api-reference/assistants)\n\n\u003e [!WARNING]\n\u003e Beta Feature. API subject to breaking changes.\n\nBuild assistants that can call models and use tools to perform tasks.\n\n- [Assistants Guide](https://platform.openai.com/docs/assistants)\n- [OpenAI Assistants Cookbook](https://github.com/openai/openai-cookbook/blob/main/examples/Assistants_API_overview_python.ipynb)\n\nThe Assistants API is accessed via `OpenAIClient.AssistantsEndpoint`\n\n#### [List Assistants](https://platform.openai.com/docs/api-reference/assistants/listAssistants)\n\nReturns a list of assistants.\n\n```csharp\nusing var api = new OpenAIClient();\nvar assistantsList = await api.AssistantsEndpoint.ListAssistantsAsync();\n\nforeach (var assistant in assistantsList.Items)\n{\n    Console.WriteLine($\"{assistant} -\u003e {assistant.CreatedAt}\");\n}\n```\n\n#### [Create Assistant](https://platform.openai.com/docs/api-reference/assistants/createAssistant)\n\nCreate an assistant with a model and instructions.\n\n```csharp\nusing var api = new OpenAIClient();\nvar request = new CreateAssistantRequest(Model.GPT4o);\nvar assistant = await api.AssistantsEndpoint.CreateAssistantAsync(request);\n```\n\n#### [Retrieve Assistant](https://platform.openai.com/docs/api-reference/assistants/getAssistant)\n\nRetrieves an assistant.\n\n```csharp\nusing var api = new OpenAIClient();\nvar assistant = await api.AssistantsEndpoint.RetrieveAssistantAsync(\"assistant-id\");\nConsole.WriteLine($\"{assistant} -\u003e {assistant.CreatedAt}\");\n```\n\n#### [Modify Assistant](https://platform.openai.com/docs/api-reference/assistants/modifyAssistant)\n\nModifies an assistant.\n\n```csharp\nusing var api = new OpenAIClient();\nvar createRequest = new CreateAssistantRequest(Model.GPT4_Turbo);\nvar assistant = await api.AssistantsEndpoint.CreateAssistantAsync(createRequest);\nvar modifyRequest = new CreateAssistantRequest(Model.GPT4o);\nvar modifiedAssistant = await api.AssistantsEndpoint.ModifyAssistantAsync(assistant.Id, modifyRequest);\n// OR AssistantExtension for easier use!\nvar modifiedAssistantEx = await assistant.ModifyAsync(modifyRequest);\n```\n\n#### [Delete Assistant](https://platform.openai.com/docs/api-reference/assistants/deleteAssistant)\n\nDelete an assistant.\n\n```csharp\nusing var api = new OpenAIClient();\nvar isDeleted = await api.AssistantsEndpoint.DeleteAssistantAsync(\"assistant-id\");\n// OR AssistantExtension for easier use!\nvar isDeleted = await assistant.DeleteAsync();\nAssert.IsTrue(isDeleted);\n```\n\n#### [Assistant Streaming](https://platform.openai.com/docs/api-reference/assistants-streaming)\n\n\u003e [!NOTE]\n\u003e Assistant stream events can be easily added to existing thread calls by passing `Func\u003cIServerSentEvent, Task\u003e streamEventHandler` callback to any existing method that supports streaming.\n\n#### [Threads](https://platform.openai.com/docs/api-reference/threads)\n\nCreate Threads that [Assistants](#assistants) can interact with.\n\nThe Threads API is accessed via `OpenAIClient.ThreadsEndpoint`\n\n##### [Create Thread](https://platform.openai.com/docs/api-reference/threads/createThread)\n\nCreate a thread.\n\n```csharp\nusing var api = new OpenAIClient();\nvar thread = await api.ThreadsEndpoint.CreateThreadAsync();\nConsole.WriteLine($\"Create thread {thread.Id} -\u003e {thread.CreatedAt}\");\n```\n\n##### [Create Thread and Run](https://platform.openai.com/docs/api-reference/runs/createThreadAndRun)\n\nCreate a thread and run it in one request.\n\n\u003e See also: [Thread Runs](#thread-runs)\n\n```csharp\nusing var api = new OpenAIClient();\nvar assistant = await api.AssistantsEndpoint.CreateAssistantAsync(\n    new CreateAssistantRequest(\n        name: \"Math Tutor\",\n        instructions: \"You are a personal math tutor. Answer questions briefly, in a sentence or less.\",\n        model: Model.GPT4o));\nvar messages = new List\u003cMessage\u003e { \"I need to solve the equation `3x + 11 = 14`. Can you help me?\" };\nvar threadRequest = new CreateThreadRequest(messages);\nvar run = await assistant.CreateThreadAndRunAsync(threadRequest);\nConsole.WriteLine($\"Created thread and run: {run.ThreadId} -\u003e {run.Id} -\u003e {run.CreatedAt}\");\n```\n\n###### Create Thread and Run Streaming\n\nCreate a thread and run it in one request while streaming events.\n\n```csharp\nusing var api = new OpenAIClient();\nvar tools = new List\u003cTool\u003e\n{\n    Tool.GetOrCreateTool(typeof(WeatherService), nameof(WeatherService.GetCurrentWeatherAsync))\n};\nvar assistantRequest = new CreateAssistantRequest(tools: tools, instructions: \"You are a helpful weather assistant. Use the appropriate unit based on geographical location.\");\nvar assistant = await api.AssistantsEndpoint.CreateAssistantAsync(assistantRequest);\nThreadResponse thread = null;\nasync Task StreamEventHandler(IServerSentEvent streamEvent)\n{\n    switch (streamEvent)\n    {\n        case ThreadResponse threadResponse:\n            thread = threadResponse;\n            break;\n        case RunResponse runResponse:\n            if (runResponse.Status == RunStatus.RequiresAction)\n            {\n                var toolOutputs = await assistant.GetToolOutputsAsync(runResponse);\n\n                foreach (var toolOutput in toolOutputs)\n                {\n                    Console.WriteLine($\"Tool Output: {toolOutput}\");\n                }\n\n                await runResponse.SubmitToolOutputsAsync(toolOutputs, StreamEventHandler);\n            }\n            break;\n        default:\n            Console.WriteLine(streamEvent.ToJsonString());\n            break;\n    }\n}\n\nvar run = await assistant.CreateThreadAndRunAsync(\"I'm in Kuala-Lumpur, please tell me what's the temperature now?\", StreamEventHandler);\nrun = await run.WaitForStatusChangeAsync();\nvar messages = await thread.ListMessagesAsync();\nforeach (var response in messages.Items.Reverse())\n{\n    Console.WriteLine($\"{response.Role}: {response.PrintContent()}\");\n}\n```\n\n##### [Retrieve Thread](https://platform.openai.com/docs/api-reference/threads/getThread)\n\nRetrieves a thread.\n\n```csharp\nusing var api = new OpenAIClient();\nvar thread = await api.ThreadsEndpoint.RetrieveThreadAsync(\"thread-id\");\n// OR if you simply wish to get the latest state of a thread\nthread = await thread.UpdateAsync();\nConsole.WriteLine($\"Retrieve thread {thread.Id} -\u003e {thread.CreatedAt}\");\n```\n\n##### [Modify Thread](https://platform.openai.com/docs/api-reference/threads/modifyThread)\n\nModifies a thread.\n\n\u003e [!NOTE]\n\u003e Only the metadata can be modified.\n\n```csharp\nusing var api = new OpenAIClient();\nvar thread = await api.ThreadsEndpoint.CreateThreadAsync();\nvar metadata = new Dictionary\u003cstring, string\u003e\n{\n    { \"key\", \"custom thread metadata\" }\n}\nthread = await api.ThreadsEndpoint.ModifyThreadAsync(thread.Id, metadata);\n// OR use extension method for convenience!\nthread = await thread.ModifyAsync(metadata);\nConsole.WriteLine($\"Modify thread {thread.Id} -\u003e {thread.Metadata[\"key\"]}\");\n```\n\n##### [Delete Thread](https://platform.openai.com/docs/api-reference/threads/deleteThread)\n\nDelete a thread.\n\n```csharp\nusing var api = new OpenAIClient();\nvar isDeleted = await api.ThreadsEndpoint.DeleteThreadAsync(\"thread-id\");\n// OR use extension method for convenience!\nvar isDeleted = await thread.DeleteAsync();\nAssert.IsTrue(isDeleted);\n```\n\n##### [Thread Messages](https://platform.openai.com/docs/api-reference/messages)\n\nCreate messages within threads.\n\n###### [List Thread Messages](https://platform.openai.com/docs/api-reference/messages/listMessages)\n\nReturns a list of messages for a given thread.\n\n```csharp\nusing var api = new OpenAIClient();\nvar messageList = await api.ThreadsEndpoint.ListMessagesAsync(\"thread-id\");\n// OR use extension method for convenience!\nvar messageList = await thread.ListMessagesAsync();\n\nforeach (var message in messageList.Items)\n{\n    Console.WriteLine($\"{message.Id}: {message.Role}: {message.PrintContent()}\");\n}\n```\n\n###### [Create Thread Message](https://platform.openai.com/docs/api-reference/messages/createMessage)\n\nCreate a message.\n\n```csharp\nusing var api = new OpenAIClient();\nvar thread = await api.ThreadsEndpoint.CreateThreadAsync();\nvar request = new CreateMessageRequest(\"Hello world!\");\nvar message = await api.ThreadsEndpoint.CreateMessageAsync(thread.Id, request);\n// OR use extension method for convenience!\nvar message = await thread.CreateMessageAsync(\"Hello World!\");\nConsole.WriteLine($\"{message.Id}: {message.Role}: {message.PrintContent()}\");\n```\n\n###### [Retrieve Thread Message](https://platform.openai.com/docs/api-reference/messages/getMessage)\n\nRetrieve a message.\n\n```csharp\nusing var api = new OpenAIClient();\nvar message = await api.ThreadsEndpoint.RetrieveMessageAsync(\"thread-id\", \"message-id\");\n// OR use extension methods for convenience!\nvar message = await thread.RetrieveMessageAsync(\"message-id\");\nvar message = await message.UpdateAsync();\nConsole.WriteLine($\"{message.Id}: {message.Role}: {message.PrintContent()}\");\n```\n\n###### [Modify Thread Message](https://platform.openai.com/docs/api-reference/messages/modifyMessage)\n\nModify a message.\n\n\u003e [!NOTE]\n\u003e Only the metadata can be modified.\n\n```csharp\nusing var api = new OpenAIClient();\nvar metadata = new Dictionary\u003cstring, string\u003e\n{\n    { \"key\", \"custom message metadata\" }\n};\nvar message = await api.ThreadsEndpoint.ModifyMessageAsync(\"thread-id\", \"message-id\", metadata);\n// OR use extension method for convenience!\nvar message = await message.ModifyAsync(metadata);\nConsole.WriteLine($\"Modify message metadata: {message.Id} -\u003e {message.Metadata[\"key\"]}\");\n```\n\n##### [Thread Runs](https://platform.openai.com/docs/api-reference/runs)\n\nRepresents an execution run on a thread.\n\n###### [List Thread Runs](https://platform.openai.com/docs/api-reference/runs/listRuns)\n\nReturns a list of runs belonging to a thread.\n\n```csharp\nusing var api = new OpenAIClient();\nvar runList = await api.ThreadsEndpoint.ListRunsAsync(\"thread-id\");\n// OR use extension method for convenience!\nvar runList = await thread.ListRunsAsync();\n\nforeach (var run in runList.Items)\n{\n    Console.WriteLine($\"[{run.Id}] {run.Status} | {run.CreatedAt}\");\n}\n```\n\n###### [Create Thread Run](https://platform.openai.com/docs/api-reference/runs/createRun)\n\nCreate a run.\n\n```csharp\nusing var api = new OpenAIClient();\nvar assistant = await api.AssistantsEndpoint.CreateAssistantAsync(\n    new CreateAssistantRequest(\n        name: \"Math Tutor\",\n        instructions: \"You are a personal math tutor. Answer questions briefly, in a sentence or less.\",\n        model: Model.GPT4o));\nvar thread = await api.ThreadsEndpoint.CreateThreadAsync();\nvar message = await thread.CreateMessageAsync(\"I need to solve the equation `3x + 11 = 14`. Can you help me?\");\nvar run = await thread.CreateRunAsync(assistant);\nConsole.WriteLine($\"[{run.Id}] {run.Status} | {run.CreatedAt}\");\n```\n\n###### Create Thread Run Streaming\n\nCreate a run and stream the events.\n\n```csharp\nusing var api = new OpenAIClient();\nvar assistant = await api.AssistantsEndpoint.CreateAssistantAsync(\n    new CreateAssistantRequest(\n        name: \"Math Tutor\",\n        instructions: \"You are a personal math tutor. Answer questions briefly, in a sentence or less. Your responses should be formatted in JSON.\",\n        model: Model.GPT4o,\n        responseFormat: ChatResponseFormat.Json));\nvar thread = await api.ThreadsEndpoint.CreateThreadAsync();\nvar message = await thread.CreateMessageAsync(\"I need to solve the equation `3x + 11 = 14`. Can you help me?\");\nvar run = await thread.CreateRunAsync(assistant, async streamEvent =\u003e\n{\n    Console.WriteLine(streamEvent.ToJsonString());\n    await Task.CompletedTask;\n});\nvar messages = await thread.ListMessagesAsync();\n\nforeach (var response in messages.Items.Reverse())\n{\n    Console.WriteLine($\"{response.Role}: {response.PrintContent()}\");\n}\n```\n\n###### [Retrieve Thread Run](https://platform.openai.com/docs/api-reference/runs/getRun)\n\nRetrieves a run.\n\n```csharp\nusing var api = new OpenAIClient();\nvar run = await api.ThreadsEndpoint.RetrieveRunAsync(\"thread-id\", \"run-id\");\n// OR use extension method for convenience!\nvar run = await thread.RetrieveRunAsync(\"run-id\");\nvar run = await run.UpdateAsync();\nConsole.WriteLine($\"[{run.Id}] {run.Status} | {run.CreatedAt}\");\n```\n\n###### [Modify Thread Run](https://platform.openai.com/docs/api-reference/runs/modifyRun)\n\nModifies a run.\n\n\u003e [!NOTE]\n\u003e Only the metadata can be modified.\n\n```csharp\nusing var api = new OpenAIClient();\nvar metadata = new Dictionary\u003cstring, string\u003e\n{\n    { \"key\", \"custom run metadata\" }\n};\nvar run = await api.ThreadsEndpoint.ModifyRunAsync(\"thread-id\", \"run-id\", metadata);\n// OR use extension method for convenience!\nvar run = await run.ModifyAsync(metadata);\nConsole.WriteLine($\"Modify run {run.Id} -\u003e {run.Metadata[\"key\"]}\");\n```\n\n###### [Thread Submit Tool Outputs to Run](https://platform.openai.com/docs/api-reference/runs/submitToolOutputs)\n\nWhen a run has the status: `requires_action` and `required_action.type` is `submit_tool_outputs`, this endpoint can be used to submit the outputs from the tool calls once they're all completed.\nAll outputs must be submitted in a single request.\n\n\u003e [!NOTE]\n\u003e See [Create Thread and Run Streaming](#create-thread-and-run-streaming) example on how to stream tool output events.\n\n```csharp\nusing var api = new OpenAIClient();\nvar tools = new List\u003cTool\u003e\n{\n    // Use a predefined tool\n    Tool.Retrieval, Tool.CodeInterpreter,\n    // Or create a tool from a type and the name of the method you want to use for function calling\n    Tool.GetOrCreateTool(typeof(WeatherService), nameof(WeatherService.GetCurrentWeatherAsync)),\n    // Pass in an instance of an object to call a method on it\n    Tool.GetOrCreateTool(api.ImagesEndPoint, nameof(ImagesEndpoint.GenerateImageAsync)),\n    // Define func\u003c,\u003e callbacks\n    Tool.FromFunc(\"name_of_func\", () =\u003e { /* callback function */ }),\n    Tool.FromFunc\u003cT1,T2,TResult\u003e(\"func_with_multiple_params\", (t1, t2) =\u003e { /* logic that calculates return value */ return tResult; })\n};\nvar assistantRequest = new CreateAssistantRequest(tools: tools, instructions: \"You are a helpful weather assistant. Use the appropriate unit based on geographical location.\");\nvar testAssistant = await api.AssistantsEndpoint.CreateAssistantAsync(assistantRequest);\nvar run = await testAssistant.CreateThreadAndRunAsync(\"I'm in Kuala-Lumpur, please tell me what's the temperature now?\");\n// waiting while run is Queued and InProgress\nrun = await run.WaitForStatusChangeAsync();\n\n// Invoke all of the tool call functions and return the tool outputs.\nvar toolOutputs = await testAssistant.GetToolOutputsAsync(run.RequiredAction.SubmitToolOutputs.ToolCalls);\n\nforeach (var toolOutput in toolOutputs)\n{\n    Console.WriteLine($\"tool call output: {toolOutput.Output}\");\n}\n// submit the tool outputs\nrun = await run.SubmitToolOutputsAsync(toolOutputs);\n// waiting while run in Queued and InProgress\nrun = await run.WaitForStatusChangeAsync();\nvar messages = await run.ListMessagesAsync();\n\nforeach (var message in messages.Items.OrderBy(response =\u003e response.CreatedAt))\n{\n    Console.WriteLine($\"{message.Role}: {message.PrintContent()}\");\n}\n```\n\n##### [Thread Structured Outputs](https://platform.openai.com/docs/guides/structured-outputs)\n\nStructured Outputs is the evolution of JSON mode. While both ensure valid JSON is produced, only Structured Outputs ensure schema adherence.\n\n\u003e [!IMPORTANT]\n\u003e\n\u003e - When using JSON mode, always instruct the model to produce JSON via some message in the conversation, for example via your system message. If you don't include an explicit instruction to generate JSON, the model may generate an unending stream of whitespace and the request may run continually until it reaches the token limit. To help ensure you don't forget, the API will throw an error if the string \"JSON\" does not appear somewhere in the context.\n\u003e - The JSON in the message the model returns may be partial (i.e. cut off) if `finish_reason` is length, which indicates the generation exceeded max_tokens or the conversation exceeded the token limit. To guard against this, check `finish_reason` before parsing the response.\n\nFirst define the structure of your responses. These will be used as your schema.\nThese are the objects you'll deserialize to, so be sure to use standard Json object models.\n\n```csharp\npublic class MathResponse\n{\n    [JsonInclude]\n    [JsonPropertyName(\"steps\")]\n    public IReadOnlyList\u003cMathStep\u003e Steps { get; private set; }\n\n    [JsonInclude]\n    [JsonPropertyName(\"final_answer\")]\n    public string FinalAnswer { get; private set; }\n}\n\npublic class MathStep\n{\n    [JsonInclude]\n    [JsonPropertyName(\"explanation\")]\n    public string Explanation { get; private set; }\n\n    [JsonInclude]\n    [JsonPropertyName(\"output\")]\n    public string Output { get; private set; }\n}\n```\n\nTo use, simply specify the `MathResponse` type as a generic constraint in either `CreateAssistantAsync`, `CreateRunAsync`, or `CreateThreadAndRunAsync`.\n\n```csharp\nusing var api = new OpenAIClient();\nvar assistant = await api.AssistantsEndpoint.CreateAssistantAsync\u003cMathResponse\u003e(\n    new CreateAssistantRequest(\n        name: \"Math Tutor\",\n        instructions: \"You are a helpful math tutor. Guide the user through the solution step by step.\",\n        model: \"gpt-4o-2024-08-06\"));\nThreadResponse thread = null;\n\ntry\n{\n    async Task StreamEventHandler(IServerSentEvent @event)\n    {\n        try\n        {\n            switch (@event)\n            {\n                case MessageResponse message:\n                    if (message.Status != MessageStatus.Completed)\n                    {\n                        Console.WriteLine(@event.ToJsonString());\n                        break;\n                    }\n\n                    var mathResponse = message.FromSchema\u003cMathResponse\u003e();\n\n                    for (var i = 0; i \u003c mathResponse.Steps.Count; i++)\n                    {\n                        var step = mathResponse.Steps[i];\n                        Console.WriteLine($\"Step {i}: {step.Explanation}\");\n                        Console.WriteLine($\"Result: {step.Output}\");\n                    }\n\n                    Console.WriteLine($\"Final Answer: {mathResponse.FinalAnswer}\");\n                    break;\n                default:\n                    Console.WriteLine(@event.ToJsonString());\n                    break;\n            }\n        }\n        catch (Exception e)\n        {\n            Console.WriteLine(e);\n            throw;\n        }\n\n        await Task.CompletedTask;\n    }\n\n    var run = await assistant.CreateThreadAndRunAsync(\"how can I solve 8x + 7 = -23\", StreamEventHandler);\n    thread = await run.GetThreadAsync();\n    run = await run.WaitForStatusChangeAsync();\n    Console.WriteLine($\"Created thread and run: {run.ThreadId} -\u003e {run.Id} -\u003e {run.CreatedAt}\");\n    var messages = await thread.ListMessagesAsync();\n\n    foreach (var response in messages.Items.OrderBy(response =\u003e response.CreatedAt))\n    {\n        Console.WriteLine($\"{response.Role}: {response.PrintContent()}\");\n    }\n}\nfinally\n{\n    await assistant.DeleteAsync(deleteToolResources: thread == null);\n\n    if (thread != null)\n    {\n        var isDeleted = await thread.DeleteAsync(deleteToolResources: true);\n    }\n}\n```\n\nYou can also manually create json schema json string as well, but you will be responsible for deserializing your response data:\n\n```csharp\nusing var api = new OpenAIClient();\nvar mathSchema = new JsonSchema(\"math_response\", @\"\n{\n  \"\"type\"\": \"\"object\"\",\n  \"\"properties\"\": {\n    \"\"steps\"\": {\n      \"\"type\"\": \"\"array\"\",\n      \"\"items\"\": {\n        \"\"type\"\": \"\"object\"\",\n        \"\"properties\"\": {\n          \"\"explanation\"\": {\n            \"\"type\"\": \"\"string\"\"\n          },\n          \"\"output\"\": {\n            \"\"type\"\": \"\"string\"\"\n          }\n        },\n        \"\"required\"\": [\n          \"\"explanation\"\",\n          \"\"output\"\"\n        ],\n        \"\"additionalProperties\"\": false\n      }\n    },\n    \"\"final_answer\"\": {\n      \"\"type\"\": \"\"string\"\"\n    }\n  },\n  \"\"required\"\": [\n    \"\"steps\"\",\n    \"\"final_answer\"\"\n  ],\n  \"\"additionalProperties\"\": false\n}\");\nvar assistant = await api.AssistantsEndpoint.CreateAssistantAsync(\n    new CreateAssistantRequest(\n        name: \"Math Tutor\",\n        instructions: \"You are a helpful math tutor. Guide the user through the solution step by step.\",\n        model: \"gpt-4o-2024-08-06\",\n        jsonSchema: mathSchema));\nThreadResponse thread = null;\n\ntry\n{\n    var run = await assistant.CreateThreadAndRunAsync(\"how can I solve 8x + 7 = -23\",\n        async @event =\u003e\n        {\n            Console.WriteLine(@event.ToJsonString());\n            await Task.CompletedTask;\n        });\n    thread = await run.GetThreadAsync();\n    run = await run.WaitForStatusChangeAsync();\n    Console.WriteLine($\"Created thread and run: {run.ThreadId} -\u003e {run.Id} -\u003e {run.CreatedAt}\");\n    var messages = await thread.ListMessagesAsync();\n\n    foreach (var response in messages.Items)\n    {\n        Console.WriteLine($\"{response.Role}: {response.PrintContent()}\");\n    }\n}\nfinally\n{\n    await assistant.DeleteAsync(deleteToolResources: thread == null);\n\n    if (thread != null)\n    {\n        var isDeleted = await thread.DeleteAsync(deleteToolResources: true);\n        Assert.IsTrue(isDeleted);\n    }\n}\n```\n\n###### [List Thread Run Steps](https://platform.openai.com/docs/api-reference/runs/listRunSteps)\n\nReturns a list of run steps belonging to a run.\n\n```csharp\nusing var api = new OpenAIClient();\nvar runStepList = await api.ThreadsEndpoint.ListRunStepsAsync(\"thread-id\", \"run-id\");\n// OR use extension method for convenience!\nvar runStepList = await run.ListRunStepsAsync();\n\nforeach (var runStep in runStepList.Items)\n{\n    Console.WriteLine($\"[{runStep.Id}] {runStep.Status} {runStep.CreatedAt} -\u003e {runStep.ExpiresAt}\");\n}\n```\n\n###### [Retrieve Thread Run Step](https://platform.openai.com/docs/api-reference/runs/getRunStep)\n\nRetrieves a run step.\n\n```csharp\nusing var api = new OpenAIClient();\nvar runStep = await api.ThreadsEndpoint.RetrieveRunStepAsync(\"thread-id\", \"run-id\", \"step-id\");\n// OR use extension method for convenience!\nvar runStep = await run.RetrieveRunStepAsync(\"step-id\");\nvar runStep = await runStep.UpdateAsync();\nConsole.WriteLine($\"[{runStep.Id}] {runStep.Status} {runStep.CreatedAt} -\u003e {runStep.ExpiresAt}\");\n```\n\n###### [Cancel Thread Run](https://platform.openai.com/docs/api-reference/runs/cancelRun)\n\nCancels a run that is `in_progress`.\n\n```csharp\nusing var api = new OpenAIClient();\nvar isCancelled = await api.ThreadsEndpoint.CancelRunAsync(\"thread-id\", \"run-id\");\n// OR use extension method for convenience!\nvar isCancelled = await run.CancelAsync();\nAssert.IsTrue(isCancelled);\n```\n\n#### [Vector Stores](https://platform.openai.com/docs/api-reference/vector-stores)\n\nVector stores are used to store files for use by the `file_search` tool.\n\n- [File Search Guide](https://platform.openai.com/docs/assistants/tools/file-search)\n\nThe Vector Stores API is accessed via `OpenAIClient.VectorStoresEndpoint`\n\n##### [List Vector Stores](https://platform.openai.com/docs/api-reference/vector-stores/list)\n\nReturns a list of vector stores.\n\n```csharp\nusing var api = new OpenAIClient();\nvar vectorStores = await api.VectorStoresEndpoint.ListVectorStoresAsync();\n\nforeach (var vectorStore in vectorStores.Items)\n{\n    Console.WriteLine(vectorStore);\n}\n```\n\n##### [Create Vector Store](https://platform.openai.com/docs/api-reference/vector-stores/create)\n\nCreate a vector store.\n\n```csharp\nusing var api = new OpenAIClient();\nvar createVectorStoreRequest = new CreateVectorStoreRequest(\"test-vector-store\");\nvar vectorStore = await api.VectorStoresEndpoint.CreateVectorStoreAsync(createVectorStoreRequest);\nConsole.WriteLine(vectorStore);\n```\n\n##### [Retrieve Vector Store](https://platform.openai.com/docs/api-reference/vector-stores/retrieve)\n\nRetrieves a vector store.\n\n```csharp\nusing var api = new OpenAIClient();\nvar vectorStore = await api.VectorStoresEndpoint.GetVectorStoreAsync(\"vector-store-id\");\nConsole.WriteLine(vectorStore);\n```\n\n##### [Modify Vector Store](https://platform.openai.com/docs/api-reference/vector-stores/modify)\n\nModifies a vector store.\n\n```csharp\nusing var api = new OpenAIClient();\nvar metadata = new Dictionary\u003cstring, object\u003e { { \"Test\", DateTime.UtcNow } };\nvar vectorStore = await api.VectorStoresEndpoint.ModifyVectorStoreAsync(\"vector-store-id\", metadata: metadata);\nConsole.WriteLine(vectorStore);\n```\n\n##### [Delete Vector Store](https://platform.openai.com/docs/api-reference/vector-stores/delete)\n\nDelete a vector store.\n\n```csharp\nusing var api = new OpenAIClient();\nvar isDeleted = await api.VectorStoresEndpoint.DeleteVectorStoreAsync(\"vector-store-id\");\nAssert.IsTrue(isDeleted);\n```\n\n##### [Vector Store Files](https://platform.openai.com/docs/api-reference/vector-stores-files)\n\nVector store files represent files inside a vector store.\n\n- [File Search Guide](https://platform.openai.com/docs/assistants/tools/file-search)\n\n###### [List Vector Store Files](https://platform.openai.com/docs/api-reference/vector-stores-files/listFiles)\n\nReturns a list of vector store files.\n\n```csharp\nusing var api = new OpenAIClient();\nvar files = await api.VectorStoresEndpoint.ListVectorStoreFilesAsync(\"vector-store-id\");\n\nforeach (var file in vectorStoreFiles.Items)\n{\n    Console.WriteLine(file);\n}\n```\n\n###### [Create Vector Store File](https://platform.openai.com/docs/api-reference/vector-stores-files/createFile)\n\nCreate a vector store file by attaching a file to a vector store.\n\n```csharp\nusing var api = new OpenAIClient();\nvar file = await api.VectorStoresEndpoint.CreateVectorStoreFileAsync(\"vector-store-id\", \"file-id\", new ChunkingStrategy(ChunkingStrategyType.Static));\nConsole.WriteLine(file);\n```\n\n###### [Retrieve Vector Store File](https://platform.openai.com/docs/api-reference/vector-stores-files/getFile)\n\nRetrieves a vector store file.\n\n```csharp\nusing var api = new OpenAIClient();\nvar file = await api.VectorStoresEndpoint.GetVectorStoreFileAsync(\"vector-store-id\", \"vector-store-file-id\");\nConsole.WriteLine(file);\n```\n\n###### [Delete Vector Store File](https://platform.openai.com/docs/api-reference/vector-stores-files/deleteFile)\n\nDelete a vector store file. This will remove the file from the vector store but the file itself will not be deleted. To delete the file, use the delete file endpoint.\n\n```csharp\nusing var api = new OpenAIClient();\nvar isDeleted = await api.VectorStoresEndpoint.DeleteVectorStoreFileAsync(\"vector-store-id\", vectorStoreFile);\nAssert.IsTrue(isDeleted);\n```\n\n##### [Vector Store File Batches](https://platform.openai.com/docs/api-reference/vector-stores-file-batches)\n\nVector store files represent files inside a vector store.\n\n- [File Search Guide](https://platform.openai.com/docs/assistants/tools/file-search)\n\n###### [Create Vector Store File Batch](https://platform.openai.com/docs/api-reference/vector-stores-file-batches/createBatch)\n\nCreate a vector store file batch.\n\n```csharp\nusing var api = new OpenAIClient();\nvar files = new List\u003cstring\u003e { \"file_id_1\",\"file_id_2\" };\nvar vectorStoreFileBatch = await api.VectorStoresEndpoint.CreateVectorStoreFileBatchAsync(\"vector-store-id\", files);\nConsole.WriteLine(vectorStoreFileBatch);\n```\n\n###### [Retrieve Vector Store File Batch](https://platform.openai.com/docs/api-reference/vector-stores-file-batches/getBatch)\n\nRetrieves a vector store file batch.\n\n```csharp\nusing var api = new OpenAIClient();\nvar vectorStoreFileBatch = await api.VectorStoresEndpoint.GetVectorStoreFileBatchAsync(\"vector-store-id\", \"vector-store-file-batch-id\");\n// you can also use convenience methods!\nvectorStoreFileBatch = await vectorStoreFileBatch.UpdateAsync();\nvectorStoreFileBatch = await vectorStoreFileBatch.WaitForStatusChangeAsync();\n```\n\n###### [List Files In Vector Store Batch](https://platform.openai.com/docs/api-reference/vector-stores-file-batches/listBatchFiles)\n\nReturns a list of vector store files in a batch.\n\n```csharp\nusing var api = new OpenAIClient();\nvar files = await api.VectorStoresEndpoint.ListVectorStoreBatchFilesAsync(\"vector-store-id\", \"vector-store-file-batch-id\");\n\nforeach (var file in files.Items)\n{\n    Console.WriteLine(file);\n}\n```\n\n###### [Cancel Vector Store File Batch](https://platform.openai.com/docs/api-reference/vector-stores-file-batches/cancelBatch)\n\nCancel a vector store file batch. This attempts to cancel the processing of files in this batch as soon as possible.\n\n```csharp\nusing var api = new OpenAIClient();\nvar isCancelled = await api.VectorStoresEndpoint.CancelVectorStoreFileBatchAsync(\"vector-store-id\", \"vector-store-file-batch-id\");\n```\n\n---\n\n### [Chat](https://platform.openai.com/docs/api-reference/chat)\n\nGiven a chat conversation, the model will return a chat completion response.\n\nThe Chat API is accessed via `OpenAIClient.ChatEndpoint`\n\n#### [Chat Completions](https://platform.openai.com/docs/api-reference/chat/create)\n\nCreates a completion for the chat message\n\n```csharp\nusing var api = new OpenAIClient();\nvar messages = new List\u003cMessage\u003e\n{\n    new Message(Role.System, \"You are a helpful assistant.\"),\n    new Message(Role.User, \"Who won the world series in 2020?\"),\n    new Message(Role.Assistant, \"The Los Angeles Dodgers won the World Series in 2020.\"),\n    new Message(Role.User, \"Where was it played?\"),\n};\nvar chatRequest = new ChatRequest(messages, Model.GPT4o);\nvar response = await api.ChatEndpoint.GetCompletionAsync(chatRequest);\nvar choice = response.FirstChoice;\nConsole.WriteLine($\"[{choice.Index}] {choice.Message.Role}: {choice.Message} | Finish Reason: {choice.FinishReason}\");\n```\n\n#### [Chat Streaming](https://platform.openai.com/docs/api-reference/chat/create#chat/create-stream)\n\n```csharp\nusing var api = new OpenAIClient();\nvar messages = new List\u003cMessage\u003e\n{\n    new Message(Role.System, \"You are a helpful assistant.\"),\n    new Message(Role.User, \"Who won the world series in 2020?\"),\n    new Message(Role.Assistant, \"The Los Angeles Dodgers won the World Series in 2020.\"),\n    new Message(Role.User, \"Where was it played?\"),\n};\nvar chatRequest = new ChatRequest(messages);\nvar response = await api.ChatEndpoint.StreamCompletionAsync(chatRequest, async partialResponse =\u003e\n{\n    Console.Write(partialResponse.FirstChoice.Delta.ToString());\n    await Task.CompletedTask;\n});\nvar choice = response.FirstChoice;\nConsole.WriteLine($\"[{choice.Index}] {choice.Message.Role}: {choice.Message} | Finish Reason: {choice.FinishReason}\");\n```\n\nOr if using [`IAsyncEnumerable{T}`](https://docs.microsoft.com/en-us/dotnet/api/system.collections.generic.iasyncenumerable-1?view=net-5.0) ([C# 8.0+](https://docs.microsoft.com/en-us/archive/msdn-magazine/2019/november/csharp-iterating-with-async-enumerables-in-csharp-8))\n\n```csharp\nusing var api = new OpenAIClient();\nvar messages = new List\u003cMessage\u003e\n{\n    new Message(Role.System, \"You are a helpful assistant.\"),\n    new Message(Role.User, \"Who won the world series in 2020?\"),\n    new Message(Role.Assistant, \"The Los Angeles Dodgers won the World Series in 2020.\"),\n    new Message(Role.User, \"Where was it played?\"),\n};\nvar cumulativeDelta = string.Empty;\nvar chatRequest = new ChatRequest(messages);\nawait foreach (var partialResponse in api.ChatEndpoint.StreamCompletionEnumerableAsync(chatRequest))\n{\n    foreach (var choice in partialResponse.Choices.Where(choice =\u003e choice.Delta?.Content != null))\n    {\n        cumulativeDelta += choice.Delta.Content;\n    }\n}\n\nConsole.WriteLine(cumulativeDelta);\n```\n\n#### [Chat Tools](https://platform.openai.com/docs/guides/function-calling)\n\n```csharp\nusing var api = new OpenAIClient();\nvar messages = new List\u003cMessage\u003e\n{\n    new(Role.System, \"You are a helpful weather assistant. Always prompt the user for their location.\"),\n    new Message(Role.User, \"What's the weather like today?\"),\n};\n\nforeach (var message in messages)\n{\n    Console.WriteLine($\"{message.Role}: {message}\");\n}\n\n// Define the tools that the assistant is able to use:\n// 1. Get a list of all the static methods decorated with FunctionAttribute\nvar tools = Tool.GetAllAvailableTools(includeDefaults: false, forceUpdate: true, clearCache: true);\n// 2. Define a custom list of tools:\nvar tools = new List\u003cTool\u003e\n{\n    Tool.GetOrCreateTool(objectInstance, \"TheNameOfTheMethodToCall\"),\n    Tool.FromFunc(\"a_custom_name_for_your_function\", ()=\u003e { /* Some logic to run */ })\n};\nvar chatRequest = new ChatRequest(messages, tools: tools, toolChoice: \"auto\");\nvar response = await api.ChatEndpoint.GetCompletionAsync(chatRequest);\nmessages.Add(response.FirstChoice.Message);\n\nConsole.WriteLine($\"{response.FirstChoice.Message.Role}: {response.FirstChoice} | Finish Reason: {response.FirstChoice.FinishReason}\");\n\nvar locationMessage = new Message(Role.User, \"I'm in Glasgow, Scotland\");\nmessages.Add(locationMessage);\nConsole.WriteLine($\"{locationMessage.Role}: {locationMessage.Content}\");\nchatRequest = new ChatRequest(messages, tools: tools, toolChoice: \"auto\");\nresponse = await api.ChatEndpoint.GetCompletionAsync(chatRequest);\n\nmessages.Add(response.FirstChoice.Message);\n\nif (response.FirstChoice.FinishReason == \"stop\")\n{\n    Console.WriteLine($\"{response.FirstChoice.Message.Role}: {response.FirstChoice} | Finish Reason: {response.FirstChoice.FinishReason}\");\n\n    var unitMessage = new Message(Role.User, \"Fahrenheit\");\n    messages.Add(unitMessage);\n    Console.WriteLine($\"{unitMessage.Role}: {unitMessage.Content}\");\n    chatRequest = new ChatRequest(messages, tools: tools, toolChoice: \"auto\");\n    response = await api.ChatEndpoint.GetCompletionAsync(chatRequest);\n}\n\n// iterate over all tool calls and invoke them\nforeach (var toolCall in response.FirstChoice.Message.ToolCalls)\n{\n    Console.WriteLine($\"{response.FirstChoice.Message.Role}: {toolCall.Function.Name} | Finish Reason: {response.FirstChoice.FinishReason}\");\n    Console.WriteLine($\"{toolCall.Function.Arguments}\");\n    // Invokes function to get a generic json result to return for tool call.\n    var functionResult = await toolCall.InvokeFunctionAsync();\n    // If you know the return type and do additional processing you can use generic overload\n    var functionResult = await toolCall.InvokeFunctionAsync\u003cstring\u003e();\n    messages.Add(new Message(toolCall, functionResult));\n    Console.WriteLine($\"{Role.Tool}: {functionResult}\");\n}\n// System: You are a helpful weather assistant.\n// User: What's the weather like today?\n// Assistant: Sure, may I know your current location? | Finish Reason: stop\n// User: I'm in Glasgow, Scotland\n// Assistant: GetCurrentWeather | Finish Reason: tool_calls\n// {\n//   \"location\": \"Glasgow, Scotland\",\n//   \"unit\": \"celsius\"\n// }\n// Tool: The current weather in Glasgow, Scotland is 39°C.\n```\n\n#### [Chat Vision](https://platform.openai.com/docs/guides/vision)\n\n\u003e [!WARNING]\n\u003e Beta Feature. API subject to breaking changes.\n\n```csharp\nusing var api = new OpenAIClient();\nvar messages = new List\u003cMessage\u003e\n{\n    new Message(Role.System, \"You are a helpful assistant.\"),\n    new Message(Role.User, new List\u003cContent\u003e\n    {\n        \"What's in this image?\",\n        new ImageUrl(\"https://upload.wikimedia.org/wikipedia/commons/thumb/d/dd/Gfp-wisconsin-madison-the-nature-boardwalk.jpg/2560px-Gfp-wisconsin-madison-the-nature-boardwalk.jpg\", ImageDetail.Low)\n    })\n};\nvar chatRequest = new ChatRequest(messages, model: Model.GPT4o);\nvar response = await api.ChatEndpoint.GetCompletionAsync(chatRequest);\nConsole.WriteLine($\"{response.FirstChoice.Message.Role}: {response.FirstChoice.Message.Content} | Finish Reason: {response.FirstChoice.FinishDetails}\");\n```\n\n#### [Chat Audio](https://platform.openai.com/docs/guides/audio)\n\n```csharp\nusing var api = new OpenAIClient();\nvar messages = new List\u003cMessage\u003e\n{\n    new Message(Role.System, \"You are a helpful assistant.\"),\n    new Message(Role.User, \"Is a golden retriever a good family dog?\")\n};\nvar chatRequest = new ChatRequest(messages, Model.GPT4oAudio, audioConfig: Voice.Alloy);\nvar response = await api.ChatEndpoint.GetCompletionAsync(chatRequest);\nConsole.WriteLine($\"{response.FirstChoice.Message.Role}: {response.FirstChoice} | Finish Reason: {response.FirstChoice.FinishDetails}\");\n// todo play response.FirstChoice.Message.AudioOutput.Data\n```\n\n#### [Chat Structured Outputs](https://platform.openai.com/docs/guides/structured-outputs)\n\nThe evolution of  [Json Mode](#chat-json-mode). While both ensure valid JSON is produced, only Structured Outputs ensure schema adherence.\n\n\u003e [!IMPORTANT]\n\u003e\n\u003e - When using JSON mode, always instruct the model to produce JSON via some message in the conversation, for example via your system message. If you don't include an explicit instruction to generate JSON, the model may generate an unending stream of whitespace and the request may run continually until it reaches the token limit. To help ensure you don't forget, the API will throw an error if the string \"JSON\" does not appear somewhere in the context.\n\u003e - The JSON in the message the model returns may be partial (i.e. cut off) if `finish_reason` is length, which indicates the generation exceeded max_tokens or the conversation exceeded the token limit. To guard against this, check `finish_reason` before parsing the response.\n\nFirst define the structure of your responses. These will be used as your schema.\nThese are the objects you'll deserialize to, so be sure to use standard Json object models.\n\n```csharp\npublic class MathResponse\n{\n    [JsonInclude]\n    [JsonPropertyName(\"steps\")]\n    public IReadOnlyList\u003cMathStep\u003e Steps { get; private set; }\n\n    [JsonInclude]\n    [JsonPropertyName(\"final_answer\")]\n    public string FinalAnswer { get; private set; }\n}\n\npublic class MathStep\n{\n    [JsonInclude]\n    [JsonPropertyName(\"explanation\")]\n    public string Explanation { get; private set; }\n\n    [JsonInclude]\n    [JsonPropertyName(\"output\")]\n    public string Output { get; private set; }\n}\n```\n\nTo use, simply specify the `MathResponse` type as a generic constraint when requesting a completion.\n\n```csharp\nusing var api = new OpenAIClient();\nvar messages = new List\u003cMessage\u003e\n{\n    new(Role.System, \"You are a helpful math tutor. Guide the user through the solution step by step.\"),\n    new(Role.User, \"how can I solve 8x + 7 = -23\")\n};\n\nvar chatRequest = new ChatRequest(messages, model: \"gpt-4o-2024-08-06\");\nvar (mathResponse, chatResponse) = await api.ChatEndpoint.GetCompletionAsync\u003cMathResponse\u003e(chatRequest);\n\nfor (var i = 0; i \u003c mathResponse.Steps.Count; i++)\n{\n    var step = mathResponse.Steps[i];\n    Console.WriteLine($\"Step {i}: {step.Explanation}\");\n    Console.WriteLine($\"Result: {step.Output}\");\n}\n\nConsole.WriteLine($\"Final Answer: {mathResponse.FinalAnswer}\");\nchatResponse.GetUsage();\n```\n\n#### [Chat Json Mode](https://platform.openai.com/docs/guides/text-generation/json-mode)\n\n\u003e [!IMPORTANT]\n\u003e\n\u003e - When using JSON mode, always instruct the model to produce JSON via some message in the conversation, for example via your system message. If you don't include an explicit instruction to generate JSON, the model may generate an unending stream of whitespace and the request may run continually until it reaches the token limit. To help ensure you don't forget, the API will throw an error if the string \"JSON\" does not appear somewhere in the context.\n\u003e - The JSON in the message the model returns may be partial (i.e. cut off) if `finish_reason` is length, which indicates the generation exceeded max_tokens or the conversation exceeded the token limit. To guard against this, check `finish_reason` before parsing the response.\n\u003e - JSON mode will not guarantee the output matches any specific schema, only that it is valid and parses without errors.\n\n```csharp\nvar messages = new List\u003cMessage\u003e\n{\n    new Message(Role.System, \"You are a helpful assistant designed to output JSON.\"),\n    new Message(Role.User, \"Who won the world series in 2020?\"),\n};\nvar chatRequest = new ChatRequest(messages, Model.GPT4o, responseFormat: ChatResponseFormat.Json);\nvar response = await api.ChatEndpoint.GetCompletionAsync(chatRequest);\n\nforeach (var choice in response.Choices)\n{\n    Console.WriteLine($\"[{choice.Index}] {choice.Message.Role}: {choice} | Finish Reason: {choice.FinishReason}\");\n}\n\nresponse.GetUsage();\n```\n\n---\n\n### [Audio](https://platform.openai.com/docs/api-reference/audio)\n\nConverts audio into text.\n\nThe Audio API is accessed via `OpenAIClient.AudioEndpoint`\n\n#### [Create Speech](https://platform.openai.com/docs/api-reference/audio/createSpeech)\n\nGenerates audio from the input text.\n\n```csharp\nusing var api = new OpenAIClient();\nvar request = new SpeechRequest(\"Hello World!\");\nasync Task ChunkCallback(ReadOnlyMemory\u003cbyte\u003e chunkCallback)\n{\n    // TODO Implement audio playback as chunks arrive\n    await Task.CompletedTask;\n}\n\nvar response = await api.AudioEndpoint.CreateSpeechAsync(request, ChunkCallback);\nawait File.WriteAllBytesAsync(\"../../../Assets/HelloWorld.mp3\", response.ToArray());\n```\n\n#### [Create Transcription](https://platform.openai.com/docs/api-reference/audio/createTranscription)\n\nTranscribes audio into the input language.\n\n```csharp\nusing var api = new OpenAIClient();\nusing var request = new AudioTranscriptionRequest(Path.GetFullPath(audioAssetPath), language: \"en\");\nvar response = await api.AudioEndpoint.CreateTranscriptionTextAsync(request);\nConsole.WriteLine(response);\n```\n\nYou can also get detailed information using `verbose_json` to get timestamp granularities:\n\n```csharp\nusing var api = new OpenAIClient();\nusing var request = new AudioTranscriptionRequest(transcriptionAudio, responseFormat: AudioResponseFormat.Verbose_Json, timestampGranularity: TimestampGranularity.Word, temperature: 0.1f, language: \"en\");\nvar response = await api.AudioEndpoint.CreateTranscriptionTextAsync(request);\n\nforeach (var word in response.Words)\n{\n    Console.WriteLine($\"[{word.Start}-{word.End}] \\\"{word.Word}\\\"\");\n}\n```\n\n#### [Create Translation](https://platform.openai.com/docs/api-reference/audio/createTranslation)\n\nTranslates audio into into English.\n\n```csharp\nusing var api = new OpenAIClient();\nusing var request = new AudioTranslationRequest(Path.GetFullPath(audioAssetPath));\nvar response = await api.AudioEndpoint.CreateTranslationTextAsync(request);\nConsole.WriteLine(response);\n```\n\n---\n\n### [Images](https://platform.openai.com/docs/api-reference/images)\n\nGiven a prompt and/or an input image, the model will generate a new image.\n\nThe Images API is accessed via `OpenAIClient.ImagesEndpoint`\n\n#### [Create Image](https://platform.openai.com/docs/api-reference/images/create)\n\nCreates an image given a prompt.\n\n```csharp\nusing var api = new OpenAIClient();\nvar request = new ImageGenerationRequest(\"A house riding a velociraptor\", Models.Model.DallE_3);\nvar imageResults = await api.ImagesEndPoint.GenerateImageAsync(request);\n\nforeach (var image in imageResults)\n{\n    Console.WriteLine(image);\n    // image == url or b64_string\n}\n```\n\n#### [Edit Image](https://platform.openai.com/docs/api-reference/images/create-edit)\n\nCreates an edited or extended image given an original image and a prompt.\n\n```csharp\nusing var api = new OpenAIClient();\nvar request = new ImageEditRequest(imageAssetPath, maskAssetPath, \"A sunlit indoor lounge area with a pool containing a flamingo\", size: ImageSize.Small);\nvar imageResults = await api.ImagesEndPoint.CreateImageEditAsync(request);\n\nforeach (var image in imageResults)\n{\n    Console.WriteLine(image);\n    // image == url or b64_string\n}\n```\n\n#### [Create Image Variation](https://platform.openai.com/docs/api-reference/images/create-variation)\n\nCreates a variation of a given image.\n\n```csharp\nusing var api = new OpenAIClient();\nvar request = new ImageVariationRequest(imageAssetPath, size: ImageSize.Small);\nvar imageResults = await api.ImagesEndPoint.CreateImageVariationAsync(request);\n\nforeach (var image in imageResults)\n{\n    Console.WriteLine(image);\n    // image == url or b64_string\n}\n```\n\n---\n\n### [Files](https://platform.openai.com/docs/api-reference/files)\n\nFiles are used to upload documents that can be used with features like [Fine-tuning](#fine-tuning).\n\nThe Files API is accessed via `OpenAIClient.FilesEndpoint`\n\n#### [List Files](https://platform.openai.com/docs/api-reference/files/list)\n\nReturns a list of files that belong to the user's organization.\n\n```csharp\nusing var api = new OpenAIClient();\nvar fileList = await api.FilesEndpoint.ListFilesAsync();\n\nforeach (var file in fileList)\n{\n    Console.WriteLine($\"{file.Id} -\u003e {file.Object}: {file.FileName} | {file.Size} bytes\");\n}\n```\n\n#### [Upload File](https://platform.openai.com/docs/api-reference/files/create)\n\nUpload a file that can be used across various endpoints. The size of all the files uploaded by one organization can be up to 100 GB.\n\nThe size of individual files can be a maximum of 512 MB. See the Assistants Tools guide to learn more about the types of files supported. The Fine-tuning API only supports .jsonl files.\n\n```csharp\nusing var api = new OpenAIClient();\nvar file = await api.FilesEndpoint.UploadFileAsync(\"path/to/your/file.jsonl\", FilePurpose.FineTune);\nConsole.WriteLine(file.Id);\n```\n\n#### [Delete File](https://platform.openai.com/docs/api-reference/files/delete)\n\nDelete a file.\n\n```csharp\nusing var api = new OpenAIClient();\nvar isDeleted = await api.FilesEndpoint.DeleteFileAsync(fileId);\nAssert.IsTrue(isDeleted);\n```\n\n#### [Retrieve File Info](https://platform.openai.com/docs/api-reference/files/retrieve)\n\nReturns information about a specific file.\n\n```csharp\nusing var api = new OpenAIClient();\nvar file = await api.FilesEndpoint.GetFileInfoAsync(fileId);\nConsole.WriteLine($\"{file.Id} -\u003e {file.Object}: {file.FileName} | {file.Size} bytes\");\n```\n\n#### [Download File Content](https://platform.openai.com/docs/api-reference/files/retrieve-content)\n\nDownloads the file content to the specified directory.\n\n```csharp\nusing var api = new OpenAIClient();\nvar downloadedFilePath = await api.FilesEndpoint.DownloadFileAsync(fileId, \"path/to/your/save/directory\");\nConsole.WriteLine(downloadedFilePath);\nAssert.IsTrue(File.Exists(downloadedFilePath));\n```\n\n---\n\n### [Fine Tuning](https://platform.openai.com/docs/api-reference/fine-tuning)\n\nManage fine-tuning jobs to tailor a model to your specific training data.\n\nRelated guide: [Fine-tune models](https://platform.openai.com/docs/guides/fine-tuning)\n\nThe Files API is accessed via `OpenAIClient.FineTuningEndpoint`\n\n#### [Create Fine Tune Job](https://platform.openai.com/docs/api-reference/fine-tuning/create)\n\nCreates a job that fine-tunes a specified model from a given dataset.\n\nResponse includes details of the enqueued job including job status and the name of the fine-tuned models once complete.\n\n```csharp\nusing var api = new OpenAIClient();\nvar fileId = \"file-abc123\";\nvar request = new CreateFineTuneRequest(fileId);\nvar job = await api.FineTuningEndpoint.CreateJobAsync(Model.GPT3_5_Turbo, request);\nConsole.WriteLine($\"Started {job.Id} | Status: {job.Status}\");\n```\n\n#### [List Fine Tune Jobs](https://platform.openai.com/docs/api-reference/fine-tuning/list)\n\nList your organization's fine-tuning jobs.\n\n```csharp\nusing var api = new OpenAIClient();\nvar jobList = await api.FineTuningEndpoint.ListJobsAsync();\n\nforeach (var job in jobList.Items.OrderByDescending(job =\u003e job.CreatedAt))\n{\n    Console.WriteLine($\"{job.Id} -\u003e {job.CreatedAt} | {job.Status}\");\n}\n```\n\n#### [Retrieve Fine Tune Job Info](https://platform.openai.com/docs/api-reference/fine-tuning/retrieve)\n\nGets info about the fine-tune job.\n\n```csharp\nusing var api = new OpenAIClient();\nvar job = await api.FineTuningEndpoint.GetJobInfoAsync(fineTuneJob);\nConsole.WriteLine($\"{job.Id} -\u003e {job.CreatedAt} | {job.Status}\");\n```\n\n#### [Cancel Fine Tune Job](https://platform.openai.com/docs/api-reference/fine-tuning/cancel)\n\nImmediately cancel a fine-tune job.\n\n```csharp\nusing var api = new OpenAIClient();\nvar isCancelled = await api.FineTuningEndpoint.CancelFineTuneJobAsync(fineTuneJob);\nAssert.IsTrue(isCancelled);\n```\n\n#### [List Fine Tune Job Events](https://platform.openai.com/docs/api-reference/fine-tuning/list-events)\n\nGet status updates for a fine-tuning job.\n\n```csharp\nusing var api = new OpenAIClient();\nvar eventList = await api.FineTuningEndpoint.ListJobEventsAsync(fineTuneJob);\nConsole.WriteLine($\"{fineTuneJob.Id} -\u003e status: {fineTuneJob.Status} | event count: {eventList.Events.Count}\");\n\nforeach (var @event in eventList.Items.OrderByDescending(@event =\u003e @event.CreatedAt))\n{\n    Console.WriteLine($\"  {@event.CreatedAt} [{@event.Level}] {@event.Message}\");\n}\n```\n\n---\n\n### [Batches](https://platform.openai.com/docs/api-reference/batch)\n\nCreate large batches of API requests for asynchronous processing. The Batch API returns completions within 24 hours for a 50% discount.\n\n- [Batch Guide](https://platform.openai.com/docs/guides/batch)\n\nThe Batches API is accessed via `OpenAIClient.BatchesEndpoint`\n\n#### [List Batches](https://platform.openai.com/docs/api-reference/batch/list)\n\nList your organization's batches.\n\n```csharp\nusing var api = new OpenAIClient();\nvar batches = await api.BatchEndpoint.ListBatchesAsync();\n\nforeach (var batch in listResponse.Items)\n{\n    Console.WriteLine(batch);\n}\n```\n\n#### [Create Batch](https://platform.openai.com/docs/api-reference/batch/create)\n\nCreates and executes a batch from an uploaded file of requests\n\n```csharp\nusing var api = new OpenAIClient();\nvar batchRequest = new CreateBatchRequest(\"file-id\", Endpoint.ChatCompletions);\nvar batch = await api.BatchEndpoint.CreateBatchAsync(batchRequest);\n```\n\n#### [Retrieve Batch](https://platform.openai.com/docs/api-reference/batch/retrieve)\n\nRetrieves a batch.\n\n```csharp\nusing var api = new OpenAIClient();\nvar batch = await api.BatchEndpoint.RetrieveBatchAsync(\"batch-id\");\n// you can also use convenience methods!\nbatch = await batch.UpdateAsync();\nbatch = await batch.WaitForStatusChangeAsync();\n```\n\n#### [Cancel Batch](https://platform.openai.com/docs/api-reference/batch/cancel)\n\nCancels an in-progress batch. The batch will be in status cancelling for up to 10 minutes, before changing to cancelled, where it will have partial results (if any) available in the output file.\n\n```csharp\nusing var api = new OpenAIClient();\nvar isCancelled = await api.BatchEndpoint.CancelBatchAsync(batch);\nAssert.IsTrue(isCancelled);\n```\n\n---\n\n### [Embeddings](https://platform.openai.com/docs/api-reference/embeddings)\n\nGet a vector representation of a given input that can be easily consumed by machine learning models and algorithms.\n\nRelated guide: [Embeddings](https://platform.openai.com/docs/guides/embeddings)\n\nThe Edits API is accessed via `OpenAIClient.EmbeddingsEndpoint`\n\n#### [Create Embeddings](https://platform.openai.com/docs/api-reference/embeddings/create)\n\nCreates an embedding vector representing the input text.\n\n```csharp\nusing var api = new OpenAIClient();\nvar response = await api.EmbeddingsEndpoint.CreateEmbeddingAsync(\"The food was delicious and the waiter...\", Models.Embedding_Ada_002);\nConsole.WriteLine(response);\n```\n\n---\n\n### [Moderations](https://platform.openai.com/docs/api-reference/moderations)\n\nGiven a input text, outputs if the model classifies it as violating OpenAI's content policy.\n\nRelated guide: [Moderations](https://platform.openai.com/docs/guides/moderation)\n\nThe Moderations API can be accessed via `OpenAIClient.ModerationsEndpoint`\n\n#### [Create Moderation](https://platform.openai.com/docs/api-reference/moderations/create)\n\nClassifies if text violates OpenAI's Content Policy.\n\n```csharp\nusing var api = new OpenAIClient();\nvar isViolation = await api.ModerationsEndpoint.GetModerationAsync(\"I want to kill them.\");\nAssert.IsTrue(isViolation);\n```\n\nAdditionally you can also get the scores of a given input.\n\n```csharp\nusing var api = new OpenAIClient();\nvar response = await api.ModerationsEndpoint.CreateModerationAsync(new ModerationsRequest(\"I love you\"));\nAssert.IsNotNull(response);\nConsole.WriteLine(response.Results?[0]?.Scores?.ToString());\n```\n\n---\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frageagainstthepixel%2Fopenai-dotnet","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frageagainstthepixel%2Fopenai-dotnet","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frageagainstthepixel%2Fopenai-dotnet/lists"}