diff --git a/.dotnet/scripts/Add-Customizations.ps1 b/.dotnet/scripts/Add-Customizations.ps1 index 8f81c806d..d0351d130 100644 --- a/.dotnet/scripts/Add-Customizations.ps1 +++ b/.dotnet/scripts/Add-Customizations.ps1 @@ -28,9 +28,13 @@ function Set-LangVersionToLatest { $root = Split-Path $PSScriptRoot -Parent $filePath = Join-Path -Path $root -ChildPath "tests\OpenAI.Tests.csproj" $xml = [xml](Get-Content -Path $filePath) + + $xml.Project.PropertyGroup.TargetFramework = "net8.0" + $element = $xml.CreateElement("LangVersion") $element.InnerText = "latest" $xml.Project.PropertyGroup.AppendChild($element) | Out-Null + $xml.Save($filePath) } diff --git a/.dotnet/scripts/Update-ClientModel.ps1 b/.dotnet/scripts/Update-ClientModel.ps1 index 6502789ba..698ed7b05 100644 --- a/.dotnet/scripts/Update-ClientModel.ps1 +++ b/.dotnet/scripts/Update-ClientModel.ps1 @@ -6,13 +6,13 @@ function Update-SystemClientModelPackage { $directory = Join-Path -Path $root -ChildPath "src" Set-Location -Path $directory dotnet remove "OpenAI.csproj" package "System.ClientModel" - dotnet add "OpenAI.csproj" package "System.ClientModel" --version "1.1.0-alpha.20240305.1" --source "https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-net/nuget/v3/index.json" + dotnet add "OpenAI.csproj" package "System.ClientModel" --version "1.1.0-alpha.20240319.1" --source "https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-net/nuget/v3/index.json" # Update System.ClientModel package in OpenAI.Tests.csproj $directory = Join-Path -Path $root -ChildPath "tests" Set-Location -Path $directory dotnet remove "OpenAI.Tests.csproj" package "System.ClientModel" - dotnet add "OpenAI.Tests.csproj" package "System.ClientModel" --version "1.1.0-alpha.20240305.1" --source "https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-net/nuget/v3/index.json" + dotnet add "OpenAI.Tests.csproj" package "System.ClientModel" --version "1.1.0-alpha.20240319.1" --source "https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-net/nuget/v3/index.json" Set-Location -Path $current } diff --git a/.dotnet/src/Custom/Assistants/AssistantClient.Protocol.cs b/.dotnet/src/Custom/Assistants/AssistantClient.Protocol.cs index 063cdab2e..0fb6ac5a0 100644 --- a/.dotnet/src/Custom/Assistants/AssistantClient.Protocol.cs +++ b/.dotnet/src/Custom/Assistants/AssistantClient.Protocol.cs @@ -122,7 +122,7 @@ public virtual async Task GetAssistantFileAssociationAsync( /// [EditorBrowsable(EditorBrowsableState.Never)] - public virtual ClientResult GetAssistantFileAssociation( + public virtual ClientResult GetAssistantFileAssociations( string assistantId, int? maxResults, string createdSortOrder, @@ -133,7 +133,7 @@ public virtual ClientResult GetAssistantFileAssociation( /// [EditorBrowsable(EditorBrowsableState.Never)] - public virtual async Task GetAssistantFileAssociationAsync( + public virtual async Task GetAssistantFileAssociationsAsync( string assistantId, int? maxResults, string createdSortOrder, @@ -248,6 +248,24 @@ public virtual async Task GetMessageAsync( RequestOptions options) => await MessageShim.GetMessageAsync(threadId, messageId, options).ConfigureAwait(false); + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public virtual ClientResult ModifyMessage( + string threadId, + string messageId, + BinaryContent content, + RequestOptions options) + => MessageShim.ModifyMessage(threadId, messageId, content, options); + + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public virtual async Task ModifyMessageAsync( + string threadId, + string messageId, + BinaryContent content, + RequestOptions options) + => await MessageShim.ModifyMessageAsync(threadId, messageId, content, options).ConfigureAwait(false); + /// [EditorBrowsable(EditorBrowsableState.Never)] public virtual ClientResult GetMessages( diff --git a/.dotnet/src/Custom/Assistants/AssistantClient.cs b/.dotnet/src/Custom/Assistants/AssistantClient.cs index 2bc2c9b72..7811b24fd 100644 --- a/.dotnet/src/Custom/Assistants/AssistantClient.cs +++ b/.dotnet/src/Custom/Assistants/AssistantClient.cs @@ -1,18 +1,18 @@ -using OpenAI.Chat; -using OpenAI.ClientShared.Internal; -using OpenAI.Internal; using System; using System.ClientModel; using System.ClientModel.Primitives; using System.Collections.Generic; -using System.Text; +using System.Diagnostics.CodeAnalysis; using System.Threading.Tasks; namespace OpenAI.Assistants; +#pragma warning disable CS1591 // public XML comments + /// /// The service client for OpenAI assistants. /// +[Experimental("OPENAI001")] public partial class AssistantClient { private OpenAIClientConnector _clientConnector; @@ -34,74 +34,17 @@ public partial class AssistantClient /// if it is defined. /// /// - /// The connection endpoint to use. /// The API key used to authenticate with the service endpoint. /// Additional options to customize the client. - public AssistantClient(Uri endpoint, ApiKeyCredential credential, OpenAIClientOptions options = null) + public AssistantClient(ApiKeyCredential credential = default, OpenAIClientOptions options = default) { options ??= new(); options.AddPolicy( new GenericActionPipelinePolicy((m) => m.Request?.Headers.Set("OpenAI-Beta", "assistants=v1")), PipelinePosition.PerCall); - _clientConnector = new("none", endpoint, credential, options); + _clientConnector = new(model: "none", credential, options); } - /// - /// Initializes a new instance of , used for assistant requests. - /// - /// - /// - /// If an endpoint is not provided, the client will use the OPENAI_ENDPOINT environment variable if it - /// defined and otherwise use the default OpenAI v1 endpoint. - /// - /// - /// If an authentication credential is not defined, the client use the OPENAI_API_KEY environment variable - /// if it is defined. - /// - /// - /// The connection endpoint to use. - /// Additional options to customize the client. - public AssistantClient(Uri endpoint, OpenAIClientOptions options = null) - : this(endpoint, credential: null, options) - { } - - /// - /// Initializes a new instance of , used for assistant requests. - /// - /// - /// - /// If an endpoint is not provided, the client will use the OPENAI_ENDPOINT environment variable if it - /// defined and otherwise use the default OpenAI v1 endpoint. - /// - /// - /// If an authentication credential is not defined, the client use the OPENAI_API_KEY environment variable - /// if it is defined. - /// - /// - /// The API key used to authenticate with the service endpoint. - /// Additional options to customize the client. - public AssistantClient(ApiKeyCredential credential, OpenAIClientOptions options = null) - : this(endpoint: null, credential, options) - { } - - /// - /// Initializes a new instance of , used for assistant requests. - /// - /// - /// - /// If an endpoint is not provided, the client will use the OPENAI_ENDPOINT environment variable if it - /// defined and otherwise use the default OpenAI v1 endpoint. - /// - /// - /// If an authentication credential is not defined, the client use the OPENAI_API_KEY environment variable - /// if it is defined. - /// - /// - /// Additional options to customize the client. - public AssistantClient(OpenAIClientOptions options = null) - : this(endpoint: null, credential: null, options) - { } - public virtual ClientResult CreateAssistant( string modelName, AssistantCreationOptions options = null) @@ -116,8 +59,7 @@ public virtual async Task> CreateAssistantAsync( AssistantCreationOptions options = null) { Internal.Models.CreateAssistantRequest request = CreateInternalCreateAssistantRequest(modelName, options); - ClientResult internalResult - = await Shim.CreateAssistantAsync(request).ConfigureAwait(false); + ClientResult internalResult = await Shim.CreateAssistantAsync(request).ConfigureAwait(false); return ClientResult.FromValue(new Assistant(internalResult.Value), internalResult.GetRawResponse()); } @@ -130,8 +72,7 @@ public virtual ClientResult GetAssistant(string assistantId) public virtual async Task> GetAssistantAsync( string assistantId) { - ClientResult internalResult - = await Shim.GetAssistantAsync(assistantId).ConfigureAwait(false); + ClientResult internalResult = await Shim.GetAssistantAsync(assistantId).ConfigureAwait(false); return ClientResult.FromValue(new Assistant(internalResult.Value), internalResult.GetRawResponse()); } @@ -177,8 +118,7 @@ public virtual async Task> ModifyAssistantAsync( AssistantModificationOptions options) { Internal.Models.ModifyAssistantRequest request = CreateInternalModifyAssistantRequest(options); - ClientResult internalResult - = await Shim.ModifyAssistantAsync(assistantId, request).ConfigureAwait(false); + ClientResult internalResult = await Shim.ModifyAssistantAsync(assistantId, request).ConfigureAwait(false); return ClientResult.FromValue(new Assistant(internalResult.Value), internalResult.GetRawResponse()); } @@ -192,8 +132,7 @@ public virtual ClientResult DeleteAssistant( public virtual async Task> DeleteAssistantAsync( string assistantId) { - ClientResult internalResponse - = await Shim.DeleteAssistantAsync(assistantId).ConfigureAwait(false); + ClientResult internalResponse = await Shim.DeleteAssistantAsync(assistantId).ConfigureAwait(false); return ClientResult.FromValue(internalResponse.Value.Deleted, internalResponse.GetRawResponse()); } @@ -210,8 +149,7 @@ public virtual async Task> CreateAssistan string assistantId, string fileId) { - ClientResult internalResult - = await Shim.CreateAssistantFileAsync(assistantId, new(fileId)).ConfigureAwait(false); + ClientResult internalResult = await Shim.CreateAssistantFileAsync(assistantId, new(fileId)).ConfigureAwait(false); return ClientResult.FromValue(new AssistantFileAssociation(internalResult.Value), internalResult.GetRawResponse()); } @@ -227,8 +165,7 @@ public virtual async Task> GetAssistantFi string assistantId, string fileId) { - ClientResult internalResult - = await Shim.GetAssistantFileAsync(assistantId, fileId).ConfigureAwait(false); + ClientResult internalResult = await Shim.GetAssistantFileAsync(assistantId, fileId).ConfigureAwait(false); return ClientResult.FromValue(new AssistantFileAssociation(internalResult.Value), internalResult.GetRawResponse()); } @@ -278,8 +215,7 @@ public virtual async Task> RemoveAssistantFileAssociationAsyn string assistantId, string fileId) { - ClientResult internalResult - = await Shim.DeleteAssistantFileAsync(assistantId, fileId).ConfigureAwait(false); + ClientResult internalResult = await Shim.DeleteAssistantFileAsync(assistantId, fileId).ConfigureAwait(false); return ClientResult.FromValue(internalResult.Value.Deleted, internalResult.GetRawResponse()); } @@ -295,8 +231,7 @@ public virtual async Task> CreateThreadAsync( ThreadCreationOptions options = null) { Internal.Models.CreateThreadRequest request = CreateInternalCreateThreadRequest(options); - ClientResult internalResult - = await ThreadShim.CreateThreadAsync(request).ConfigureAwait(false); + ClientResult internalResult = await ThreadShim.CreateThreadAsync(request).ConfigureAwait(false); return ClientResult.FromValue(new AssistantThread(internalResult.Value), internalResult.GetRawResponse()); } @@ -309,8 +244,7 @@ public virtual ClientResult GetThread(string threadId) public virtual async Task> GetThreadAsync( string threadId) { - ClientResult internalResult - = await ThreadShim.GetThreadAsync(threadId).ConfigureAwait(false); + ClientResult internalResult = await ThreadShim.GetThreadAsync(threadId).ConfigureAwait(false); return ClientResult.FromValue(new AssistantThread(internalResult.Value), internalResult.GetRawResponse()); } @@ -332,8 +266,7 @@ public virtual async Task> ModifyThreadAsync( Internal.Models.ModifyThreadRequest request = new( options.Metadata, serializedAdditionalRawData: null); - ClientResult internalResult - = await ThreadShim.ModifyThreadAsync(threadId, request); + ClientResult internalResult = await ThreadShim.ModifyThreadAsync(threadId, request).ConfigureAwait(false); return ClientResult.FromValue(new AssistantThread(internalResult.Value), internalResult.GetRawResponse()); } @@ -345,8 +278,7 @@ public virtual ClientResult DeleteThread(string threadId) public virtual async Task> DeleteThreadAsync(string threadId) { - ClientResult internalResult - = await ThreadShim.DeleteThreadAsync(threadId).ConfigureAwait(false); + ClientResult internalResult = await ThreadShim.DeleteThreadAsync(threadId).ConfigureAwait(false); return ClientResult.FromValue(internalResult.Value.Deleted, internalResult.GetRawResponse()); } @@ -378,8 +310,7 @@ public virtual async Task> CreateMessageAsync( options.FileIds, options.Metadata, serializedAdditionalRawData: null); - ClientResult internalResult - = await MessageShim.CreateMessageAsync(threadId, request).ConfigureAwait(false); + ClientResult internalResult = await MessageShim.CreateMessageAsync(threadId, request).ConfigureAwait(false); return ClientResult.FromValue(new ThreadMessage(internalResult.Value), internalResult.GetRawResponse()); } @@ -395,8 +326,31 @@ public virtual async Task> GetMessageAsync( string threadId, string messageId) { - ClientResult internalResult - = await MessageShim.GetMessageAsync(threadId, messageId).ConfigureAwait(false); + ClientResult internalResult = await MessageShim.GetMessageAsync(threadId, messageId).ConfigureAwait(false); + return ClientResult.FromValue(new ThreadMessage(internalResult.Value), internalResult.GetRawResponse()); + } + + public virtual ClientResult ModifyMessage( + string threadId, + string messageId, + MessageModificationOptions options) + { + Internal.Models.ModifyMessageRequest request = new( + options.Metadata, + serializedAdditionalRawData: null); + ClientResult internalResult = MessageShim.ModifyMessage(threadId, messageId, request); + return ClientResult.FromValue(new ThreadMessage(internalResult.Value), internalResult.GetRawResponse()); + } + + public virtual async Task> ModifyMessageAsync( + string threadId, + string messageId, + MessageModificationOptions options) + { + Internal.Models.ModifyMessageRequest request = new( + options.Metadata, + serializedAdditionalRawData: null); + ClientResult internalResult = await MessageShim.ModifyMessageAsync(threadId, messageId, request).ConfigureAwait(false); return ClientResult.FromValue(new ThreadMessage(internalResult.Value), internalResult.GetRawResponse()); } @@ -447,8 +401,7 @@ public virtual async Task> GetMessageFileAs string messageId, string fileId) { - ClientResult internalResult - = await MessageShim.GetMessageFileAsync(threadId, messageId, fileId).ConfigureAwait(false); + ClientResult internalResult = await MessageShim.GetMessageFileAsync(threadId, messageId, fileId).ConfigureAwait(false); return ClientResult.FromValue(new MessageFileAssociation(internalResult.Value), internalResult.GetRawResponse()); } @@ -504,8 +457,7 @@ public virtual async Task> CreateRunAsync( RunCreationOptions options = null) { Internal.Models.CreateRunRequest request = CreateInternalCreateRunRequest(assistantId, options); - ClientResult internalResult - = await RunShim.CreateRunAsync(threadId, request).ConfigureAwait(false); + ClientResult internalResult = await RunShim.CreateRunAsync(threadId, request).ConfigureAwait(false); return ClientResult.FromValue(new ThreadRun(internalResult.Value), internalResult.GetRawResponse()); } @@ -547,8 +499,7 @@ public virtual async Task> CreateThreadAndRunAsync( { Internal.Models.CreateThreadAndRunRequest request = CreateInternalCreateThreadAndRunRequest(assistantId, threadOptions, runOptions); - ClientResult internalResult - = await RunShim.CreateThreadAndRunAsync(request).ConfigureAwait(false); + ClientResult internalResult = await RunShim.CreateThreadAndRunAsync(request).ConfigureAwait(false); return ClientResult.FromValue(new ThreadRun(internalResult.Value), internalResult.GetRawResponse()); } @@ -580,8 +531,7 @@ public virtual ClientResult GetRun(string threadId, string runId) public virtual async Task> GetRunAsync(string threadId, string runId) { - ClientResult internalResult - = await RunShim.GetRunAsync(threadId, runId).ConfigureAwait(false); + ClientResult internalResult = await RunShim.GetRunAsync(threadId, runId).ConfigureAwait(false); return ClientResult.FromValue(new ThreadRun(internalResult.Value), internalResult.GetRawResponse()); } @@ -627,8 +577,7 @@ public virtual ClientResult ModifyRun(string threadId, string runId, public virtual async Task> ModifyRunAsync(string threadId, string runId, RunModificationOptions options) { Internal.Models.ModifyRunRequest request = new(options.Metadata, serializedAdditionalRawData: null); - ClientResult internalResult - = await RunShim.ModifyRunAsync(threadId, runId, request).ConfigureAwait(false); + ClientResult internalResult = await RunShim.ModifyRunAsync(threadId, runId, request).ConfigureAwait(false); return ClientResult.FromValue(new ThreadRun(internalResult.Value), internalResult.GetRawResponse()); } @@ -640,8 +589,7 @@ public virtual ClientResult CancelRun(string threadId, string runId) public virtual async Task> CancelRunAsync(string threadId, string runId) { - ClientResult internalResult - = await RunShim.CancelRunAsync(threadId, runId); + ClientResult internalResult = await RunShim.CancelRunAsync(threadId, runId).ConfigureAwait(false); return ClientResult.FromValue(true, internalResult.GetRawResponse()); } @@ -759,7 +707,8 @@ internal static Internal.Models.CreateAssistantRequest CreateInternalCreateAssis serializedAdditionalRawData: null); } - internal static Internal.Models.ModifyAssistantRequest CreateInternalModifyAssistantRequest(AssistantModificationOptions options) + internal static Internal.Models.ModifyAssistantRequest CreateInternalModifyAssistantRequest( + AssistantModificationOptions options) { return new Internal.Models.ModifyAssistantRequest( options.Model, @@ -772,7 +721,8 @@ internal static Internal.Models.ModifyAssistantRequest CreateInternalModifyAssis serializedAdditionalRawData: null); } - internal static Internal.Models.CreateThreadRequest CreateInternalCreateThreadRequest(ThreadCreationOptions options) + internal static Internal.Models.CreateThreadRequest CreateInternalCreateThreadRequest( + ThreadCreationOptions options) { options ??= new(); return new Internal.Models.CreateThreadRequest( @@ -879,7 +829,7 @@ internal virtual async Task>> GetListQueryPageAsyn where T : class where U : class { - ClientResult internalResult = await internalAsyncFunc.Invoke(); + ClientResult internalResult = await internalAsyncFunc.Invoke().ConfigureAwait(false); ListQueryPage convertedValue = ListQueryPage.Create(internalResult.Value) as ListQueryPage; return ClientResult.FromValue(convertedValue, internalResult.GetRawResponse()); } @@ -887,3 +837,5 @@ internal virtual async Task>> GetListQueryPageAsyn private static PipelineMessageClassifier _responseErrorClassifier200; private static PipelineMessageClassifier ResponseErrorClassifier200 => _responseErrorClassifier200 ??= PipelineMessageClassifier.Create(stackalloc ushort[] { 200 }); } + +#pragma warning restore CS1591 // public XML comments \ No newline at end of file diff --git a/.dotnet/src/Custom/Assistants/MessageModificationOptions.cs b/.dotnet/src/Custom/Assistants/MessageModificationOptions.cs new file mode 100644 index 000000000..947eccb6d --- /dev/null +++ b/.dotnet/src/Custom/Assistants/MessageModificationOptions.cs @@ -0,0 +1,21 @@ +using System.Collections.Generic; + +namespace OpenAI.Assistants; + +/// +/// Represents additional options available when modifying an existing . +/// +public partial class MessageModificationOptions +{ + /// + /// A replacement for the optional key/value mapping of additional, supplemental data items to attach to the + /// . This information may be useful for storing custom details in a structured format. + /// + /// + /// + /// Keys can be a maximum of 64 characters in length. + /// Values can be a maximum of 512 characters in length. + /// + /// + public IDictionary Metadata { get; } = new ChangeTrackingDictionary(); +} \ No newline at end of file diff --git a/.dotnet/src/Custom/Assistants/ThreadModificationOptions.cs b/.dotnet/src/Custom/Assistants/ThreadModificationOptions.cs index b36224468..8e47015d3 100644 --- a/.dotnet/src/Custom/Assistants/ThreadModificationOptions.cs +++ b/.dotnet/src/Custom/Assistants/ThreadModificationOptions.cs @@ -1,6 +1,3 @@ -using OpenAI.ClientShared.Internal; -using System.ClientModel.Internal; - using System.Collections.Generic; namespace OpenAI.Assistants; diff --git a/.dotnet/src/Custom/Assistants/ToolOutput.cs b/.dotnet/src/Custom/Assistants/ToolOutput.cs index 762466f89..623da2461 100644 --- a/.dotnet/src/Custom/Assistants/ToolOutput.cs +++ b/.dotnet/src/Custom/Assistants/ToolOutput.cs @@ -19,9 +19,4 @@ public ToolOutput(string toolCallId, string output = null) Id = toolCallId; Output = output; } - - [SetsRequiredMembers] - public ToolOutput(RequiredToolCall toolCall, string output = null) - : this(toolCall.Id, output) - { } } diff --git a/.dotnet/src/Custom/Audio/AudioClient.Protocol.cs b/.dotnet/src/Custom/Audio/AudioClient.Protocol.cs index 527e5b672..d4998eb39 100644 --- a/.dotnet/src/Custom/Audio/AudioClient.Protocol.cs +++ b/.dotnet/src/Custom/Audio/AudioClient.Protocol.cs @@ -1,6 +1,8 @@ -using System.ClientModel; +using System; +using System.ClientModel; using System.ClientModel.Primitives; using System.ComponentModel; +using System.IO; using System.Threading.Tasks; namespace OpenAI.Audio; @@ -16,4 +18,180 @@ public virtual ClientResult GenerateSpeechFromText(BinaryContent content, Reques [EditorBrowsable(EditorBrowsableState.Never)] public virtual async Task GenerateSpeechFromTextAsync(BinaryContent content, RequestOptions options = null) => await Shim.CreateSpeechAsync(content, options).ConfigureAwait(false); + + /// + /// [Protocol Method] Transcribes audio into the input language. + /// + /// + /// + /// This protocol method allows explicit creation of the request and processing of the response for advanced scenarios. + /// + /// + /// + /// + /// Please try the simpler or convenience overload with strongly typed models first. + /// + /// + /// + /// + /// The content to send as the body of the request. + /// The content type of the request. + /// The request options, which can override default behaviors of the client pipeline on a per-call basis. + /// or is null. + /// is an empty string, and was expected to be non-empty. + /// Service returned a non-success status code. + /// The response returned from the service. + [EditorBrowsable(EditorBrowsableState.Never)] + public virtual ClientResult TranscribeAudio(BinaryContent content, string contentType, RequestOptions options = null) + { + Argument.AssertNotNull(content, nameof(content)); + Argument.AssertNotNullOrEmpty(contentType, nameof(contentType)); + + options ??= new RequestOptions(); + + using PipelineMessage message = CreateCreateTranscriptionRequest(content, contentType, options); + + Shim.Pipeline.Send(message); + + PipelineResponse response = message.Response!; + + if (response.IsError && options.ErrorOptions == ClientErrorBehaviors.Default) + { + throw new ClientResultException(response); + } + + return ClientResult.FromResponse(response); + } + + /// + /// [Protocol Method] Transcribes audio into the input language. + /// + /// + /// + /// This protocol method allows explicit creation of the request and processing of the response for advanced scenarios. + /// + /// + /// + /// + /// Please try the simpler or convenience overload with strongly typed models first. + /// + /// + /// + /// + /// The content to send as the body of the request. + /// The content type of the request. + /// The request options, which can override default behaviors of the client pipeline on a per-call basis. + /// or is null. + /// is an empty string, and was expected to be non-empty. + /// Service returned a non-success status code. + /// The response returned from the service. + [EditorBrowsable(EditorBrowsableState.Never)] + public virtual async Task TranscribeAudioAsync(BinaryContent content, string contentType, RequestOptions options = null) + { + Argument.AssertNotNull(content, nameof(content)); + Argument.AssertNotNullOrEmpty(contentType, nameof(contentType)); + + options ??= new RequestOptions(); + + using PipelineMessage message = CreateCreateTranscriptionRequest(content, contentType, options); + + Shim.Pipeline.Send(message); + + PipelineResponse response = message.Response!; + + if (response.IsError && options.ErrorOptions == ClientErrorBehaviors.Default) + { + throw await ClientResultException.CreateAsync(response).ConfigureAwait(false); + } + + return ClientResult.FromResponse(response); + } + + /// + /// [Protocol Method] Translates audio into English. + /// + /// + /// + /// This protocol method allows explicit creation of the request and processing of the response for advanced scenarios. + /// + /// + /// + /// + /// Please try the simpler or convenience overload with strongly typed models first. + /// + /// + /// + /// + /// The content to send as the body of the request. + /// The content type of the request. + /// The request options, which can override default behaviors of the client pipeline on a per-call basis. + /// is null. + /// is an empty string, and was expected to be non-empty. + /// Service returned a non-success status code. + /// The response returned from the service. + [EditorBrowsable(EditorBrowsableState.Never)] + public virtual ClientResult TranslateAudio(BinaryContent content, string contentType, RequestOptions options = null) + { + Argument.AssertNotNull(content, nameof(content)); + Argument.AssertNotNullOrEmpty(contentType, nameof(contentType)); + + options ??= new RequestOptions(); + + using PipelineMessage message = CreateCreateTranslationRequest(content, contentType, options); + + Shim.Pipeline.Send(message); + + PipelineResponse response = message.Response!; + + if (response.IsError && options.ErrorOptions == ClientErrorBehaviors.Default) + { + throw new ClientResultException(response); + } + + return ClientResult.FromResponse(response); + } + + /// + /// [Protocol Method] Translates audio into English. + /// + /// + /// + /// This protocol method allows explicit creation of the request and processing of the response for advanced scenarios. + /// + /// + /// + /// + /// Please try the simpler or convenience overload with strongly typed models first. + /// + /// + /// + /// + /// The content to send as the body of the request. + /// The content type of the request. + /// The request options, which can override default behaviors of the client pipeline on a per-call basis. + /// is null. + /// is an empty string, and was expected to be non-empty. + /// Service returned a non-success status code. + /// The response returned from the service. + [EditorBrowsable(EditorBrowsableState.Never)] + public virtual async Task TranslateAudioAsync(BinaryContent content, string contentType, RequestOptions options = null) + { + Argument.AssertNotNull(content, nameof(content)); + Argument.AssertNotNullOrEmpty(contentType, nameof(contentType)); + + options ??= new RequestOptions(); + + using PipelineMessage message = CreateCreateTranslationRequest(content, contentType, options); + + Shim.Pipeline.Send(message); + + PipelineResponse response = message.Response!; + + if (response.IsError && options.ErrorOptions == ClientErrorBehaviors.Default) + { + throw await ClientResultException.CreateAsync(response).ConfigureAwait(false); + } + + return ClientResult.FromResponse(response); + } } \ No newline at end of file diff --git a/.dotnet/src/Custom/Audio/AudioClient.cs b/.dotnet/src/Custom/Audio/AudioClient.cs index 12ca6eb4f..fddee7535 100644 --- a/.dotnet/src/Custom/Audio/AudioClient.cs +++ b/.dotnet/src/Custom/Audio/AudioClient.cs @@ -1,10 +1,9 @@ -using OpenAI.ClientShared.Internal; +using OpenAI.Internal; using System; using System.ClientModel; using System.ClientModel.Primitives; -using System.Collections.Generic; +using System.IO; using System.Text; -using System.Text.Json; using System.Threading.Tasks; namespace OpenAI.Audio; @@ -28,74 +27,14 @@ public partial class AudioClient /// if it is defined. /// /// - /// The connection endpoint to use. /// The model name for audio operations that the client should use. /// The API key used to authenticate with the service endpoint. /// Additional options to customize the client. - public AudioClient(Uri endpoint, string model, ApiKeyCredential credential, OpenAIClientOptions options = null) + public AudioClient(string model, ApiKeyCredential credential = default, OpenAIClientOptions options = default) { - _clientConnector = new(model, endpoint, credential, options); + _clientConnector = new(model, credential, options); } - /// - /// Initializes a new instance of , used for audio operation requests. - /// - /// - /// - /// If an endpoint is not provided, the client will use the OPENAI_ENDPOINT environment variable if it - /// defined and otherwise use the default OpenAI v1 endpoint. - /// - /// - /// If an authentication credential is not defined, the client use the OPENAI_API_KEY environment variable - /// if it is defined. - /// - /// - /// The connection endpoint to use. - /// The model name for audio operations that the client should use. - /// Additional options to customize the client. - public AudioClient(Uri endpoint, string model, OpenAIClientOptions options = null) - : this(endpoint, model, credential: null, options) - { } - - /// - /// Initializes a new instance of , used for audio operation requests. - /// - /// - /// - /// If an endpoint is not provided, the client will use the OPENAI_ENDPOINT environment variable if it - /// defined and otherwise use the default OpenAI v1 endpoint. - /// - /// - /// If an authentication credential is not defined, the client use the OPENAI_API_KEY environment variable - /// if it is defined. - /// - /// - /// The model name for audio operations that the client should use. - /// The API key used to authenticate with the service endpoint. - /// Additional options to customize the client. - public AudioClient(string model, ApiKeyCredential credential, OpenAIClientOptions options = null) - : this(endpoint: null, model, credential, options) - { } - - /// - /// Initializes a new instance of , used for audio operation requests. - /// - /// - /// - /// If an endpoint is not provided, the client will use the OPENAI_ENDPOINT environment variable if it - /// defined and otherwise use the default OpenAI v1 endpoint. - /// - /// - /// If an authentication credential is not defined, the client use the OPENAI_API_KEY environment variable - /// if it is defined. - /// - /// - /// The model name for audio operations that the client should use. - /// Additional options to customize the client. - public AudioClient(string model, OpenAIClientOptions options = null) - : this(endpoint: null, model, credential: null, options) - { } - /// /// Creates text-to-speech audio that reflects the specified voice speaking the provided input text. /// @@ -144,173 +83,138 @@ public virtual Task> GenerateSpeechFromTextAsync( return Shim.CreateSpeechAsync(request); } - public virtual ClientResult TranscribeAudio(BinaryData audioBytes, string filename, AudioTranscriptionOptions options = null) - { - PipelineMessage message = CreateInternalTranscriptionRequestMessage(audioBytes, filename, options); - Shim.Pipeline.Send(message); - return GetTranscriptionResultFromResponse(message.Response); - } + public virtual ClientResult TranscribeAudio(FileStream audio, AudioTranscriptionOptions options = null) + => TranscribeAudio(audio, Path.GetFileName(audio.Name), options); - public virtual async Task> TranscribeAudioAsync(BinaryData audioBytes, string filename, AudioTranscriptionOptions options = null) + public virtual ClientResult TranscribeAudio(Stream audio, string fileName, AudioTranscriptionOptions options = null) { - PipelineMessage message = CreateInternalTranscriptionRequestMessage(audioBytes, filename, options); - await Shim.Pipeline.SendAsync(message).ConfigureAwait(false); - return GetTranscriptionResultFromResponse(message.Response); - } + Argument.AssertNotNull(audio, nameof(audio)); + Argument.AssertNotNull(fileName, nameof(fileName)); - public virtual ClientResult TranslateAudio(BinaryData audioBytes, string filename, AudioTranslationOptions options = null) - { - PipelineMessage message = CreateInternalTranslationRequestMessage(audioBytes, filename, options); - Shim.Pipeline.Send(message); - return GetTranslationResultFromResponse(message.Response); - } + options ??= new(); - public virtual async Task> TranslateAudioAsync(BinaryData audioBytes, string filename, AudioTranslationOptions options = null) - { - PipelineMessage message = CreateInternalTranslationRequestMessage(audioBytes, filename, options); - await Shim.Pipeline.SendAsync(message).ConfigureAwait(false); - return GetTranslationResultFromResponse(message.Response); + using MultipartFormDataBinaryContent content = options.ToMultipartContent(audio, fileName, _clientConnector.Model); + + ClientResult result = TranscribeAudio(content, content.ContentType); + + PipelineResponse response = result.GetRawResponse(); + + AudioTranscription value = AudioTranscription.Deserialize(response.Content!); + + return ClientResult.FromValue(value, response); } - private PipelineMessage CreateInternalTranscriptionRequestMessage(BinaryData audioBytes, string filename, AudioTranscriptionOptions options) + public virtual async Task> TranscribeAudioAsync(FileStream audio, AudioTranscriptionOptions options = null) + => await TranscribeAudioAsync(audio, Path.GetFileName(audio.Name), options).ConfigureAwait(false); + + public virtual async Task> TranscribeAudioAsync(Stream audio, string filename, AudioTranscriptionOptions options = null) { - PipelineMessage message = Shim.Pipeline.CreateMessage(); - message.ResponseClassifier = ResponseErrorClassifier200; - PipelineRequest request = message.Request; - request.Method = "POST"; - UriBuilder uriBuilder = new(_clientConnector.Endpoint.AbsoluteUri); - StringBuilder path = new(); - path.Append("/audio/transcriptions"); - uriBuilder.Path += path.ToString(); - request.Uri = uriBuilder.Uri; + Argument.AssertNotNull(audio, nameof(audio)); + Argument.AssertNotNull(filename, nameof(filename)); - MultipartFormDataContent requestContent = CreateInternalTranscriptionRequestContent(audioBytes, filename, options); - requestContent.ApplyToRequest(request); + options ??= new(); - return message; + using MultipartFormDataBinaryContent content = options.ToMultipartContent(audio, filename, _clientConnector.Model); + + ClientResult result = await TranscribeAudioAsync(content, content.ContentType).ConfigureAwait(false); + + PipelineResponse response = result.GetRawResponse(); + + AudioTranscription value = AudioTranscription.Deserialize(response.Content!); + + return ClientResult.FromValue(value, response); } - private PipelineMessage CreateInternalTranslationRequestMessage(BinaryData audioBytes, string filename, AudioTranslationOptions options) + private PipelineMessage CreateCreateTranscriptionRequest(BinaryContent content, string contentType, RequestOptions options) { PipelineMessage message = Shim.Pipeline.CreateMessage(); message.ResponseClassifier = ResponseErrorClassifier200; + PipelineRequest request = message.Request; request.Method = "POST"; + UriBuilder uriBuilder = new(_clientConnector.Endpoint.AbsoluteUri); + StringBuilder path = new(); - path.Append("/audio/translations"); + path.Append("/audio/transcriptions"); uriBuilder.Path += path.ToString(); + request.Uri = uriBuilder.Uri; - MultipartFormDataContent requestContent = CreateInternalTranscriptionRequestContent(audioBytes, filename, options); - requestContent.ApplyToRequest(request); + request.Headers.Set("Content-Type", contentType); + + request.Content = content; + + message.Apply(options); return message; } - private MultipartFormDataContent CreateInternalTranscriptionRequestContent(BinaryData audioBytes, string filename, AudioTranscriptionOptions options) + public virtual ClientResult TranslateAudio(FileStream audio, AudioTranslationOptions options = null) + => TranslateAudio(audio, Path.GetFileName(audio.Name), options); + + public virtual ClientResult TranslateAudio(Stream audio, string fileName, AudioTranslationOptions options = null) { + Argument.AssertNotNull(audio, nameof(audio)); + Argument.AssertNotNull(fileName, nameof(fileName)); + options ??= new(); - return CreateInternalTranscriptionRequestContent( - audioBytes, - filename, - options.Language, - options.Prompt, - options.ResponseFormat, - options.Temperature, - options.EnableWordTimestamps, - options.EnableSegmentTimestamps); + + using MultipartFormDataBinaryContent content = options.ToMultipartContent(audio, fileName, _clientConnector.Model); + + ClientResult result = TranslateAudio(content, content.ContentType); + + PipelineResponse response = result.GetRawResponse(); + + AudioTranslation value = AudioTranslation.Deserialize(response.Content!); + + return ClientResult.FromValue(value, response); } - private MultipartFormDataContent CreateInternalTranscriptionRequestContent(BinaryData audioBytes, string filename, AudioTranslationOptions options) + public virtual async Task> TranslateAudioAsync(FileStream audio, AudioTranslationOptions options = null) + => await TranslateAudioAsync(audio, Path.GetFileName(audio.Name), options).ConfigureAwait(false); + + public virtual async Task> TranslateAudioAsync(Stream audio, string fileName, AudioTranslationOptions options = null) { + Argument.AssertNotNull(audio, nameof(audio)); + Argument.AssertNotNull(fileName, nameof(fileName)); + options ??= new(); - return CreateInternalTranscriptionRequestContent( - audioBytes, - filename, - language: null, - options.Prompt, - options.ResponseFormat, - options.Temperature, - enableWordTimestamps: null, - enableSegmentTimestamps: null); - } - private MultipartFormDataContent CreateInternalTranscriptionRequestContent( - BinaryData audioBytes, - string filename, - string language = null, - string prompt = null, - AudioTranscriptionFormat? transcriptionFormat = null, - float? temperature = null, - bool? enableWordTimestamps = null, - bool? enableSegmentTimestamps = null) - { - MultipartFormDataContent content = new(); - content.Add(MultipartContent.Create(BinaryData.FromString(_clientConnector.Model)), name: "model", []); - if (Optional.IsDefined(language)) - { - content.Add(MultipartContent.Create(BinaryData.FromString(language)), name: "language", []); - } - if (Optional.IsDefined(prompt)) - { - content.Add(MultipartContent.Create(BinaryData.FromString(prompt)), name: "prompt", []); - } - if (Optional.IsDefined(transcriptionFormat)) - { - content.Add(MultipartContent.Create(BinaryData.FromString(transcriptionFormat switch - { - AudioTranscriptionFormat.Simple => "json", - AudioTranscriptionFormat.Detailed => "verbose_json", - AudioTranscriptionFormat.Srt => "srt", - AudioTranscriptionFormat.Vtt => "vtt", - _ => throw new ArgumentException(nameof(transcriptionFormat)), - })), - name: "response_format", - []); - } - if (Optional.IsDefined(temperature)) - { - content.Add(MultipartContent.Create(BinaryData.FromString($"{temperature}")), name: "temperature", []); - } - if (Optional.IsDefined(enableWordTimestamps) || Optional.IsDefined(enableSegmentTimestamps)) - { - List granularities = []; - if (enableWordTimestamps == true) - { - granularities.Add("word"); - } - if (enableSegmentTimestamps == true) - { - granularities.Add("segment"); - } - content.Add(MultipartContent.Create(BinaryData.FromObjectAsJson(granularities)), name: "timestamp_granularities", []); - } - content.Add(MultipartContent.Create(audioBytes), name: "file", fileName: filename, []); + using MultipartFormDataBinaryContent content = options.ToMultipartContent(audio, fileName, _clientConnector.Model); - return content; - } + ClientResult result = await TranslateAudioAsync(content, content.ContentType).ConfigureAwait(false); - private static ClientResult GetTranscriptionResultFromResponse(PipelineResponse response) - { - if (response.IsError) - { - throw new ClientResultException(response); - } + PipelineResponse response = result.GetRawResponse(); - using JsonDocument responseDocument = JsonDocument.Parse(response.Content); - return ClientResult.FromValue(AudioTranscription.DeserializeAudioTranscription(responseDocument.RootElement), response); + AudioTranslation value = AudioTranslation.Deserialize(response.Content!); + + return ClientResult.FromValue(value, response); } - private static ClientResult GetTranslationResultFromResponse(PipelineResponse response) + private PipelineMessage CreateCreateTranslationRequest(BinaryContent content, string contentType, RequestOptions options) { - if (response.IsError) - { - throw new ClientResultException(response); - } + PipelineMessage message = Shim.Pipeline.CreateMessage(); + message.ResponseClassifier = ResponseErrorClassifier200; - using JsonDocument responseDocument = JsonDocument.Parse(response.Content); - return ClientResult.FromValue(AudioTranslation.DeserializeAudioTranscription(responseDocument.RootElement), response); + PipelineRequest request = message.Request; + request.Method = "POST"; + + UriBuilder uriBuilder = new(_clientConnector.Endpoint.AbsoluteUri); + + StringBuilder path = new(); + path.Append("/audio/translations"); + uriBuilder.Path += path.ToString(); + + request.Uri = uriBuilder.Uri; + + request.Headers.Set("Content-Type", contentType); + + request.Content = content; + + message.Apply(options); + + return message; } private Internal.Models.CreateSpeechRequest CreateInternalTtsRequest( @@ -346,7 +250,7 @@ private Internal.Models.CreateSpeechRequest CreateInternalTtsRequest( options?.SpeedMultiplier, serializedAdditionalRawData: null); } + private static PipelineMessageClassifier _responseErrorClassifier200; private static PipelineMessageClassifier ResponseErrorClassifier200 => _responseErrorClassifier200 ??= PipelineMessageClassifier.Create(stackalloc ushort[] { 200 }); - } diff --git a/.dotnet/src/Custom/Audio/AudioTranscription.cs b/.dotnet/src/Custom/Audio/AudioTranscription.cs index 5f55ef9cb..efd5a0e8d 100644 --- a/.dotnet/src/Custom/Audio/AudioTranscription.cs +++ b/.dotnet/src/Custom/Audio/AudioTranscription.cs @@ -22,6 +22,12 @@ internal AudioTranscription(string language, TimeSpan? duration, string text, IR Segments = segments; } + internal static AudioTranscription Deserialize(BinaryData content) + { + using JsonDocument responseDocument = JsonDocument.Parse(content); + return DeserializeAudioTranscription(responseDocument.RootElement); + } + internal static AudioTranscription DeserializeAudioTranscription(JsonElement element, ModelReaderWriterOptions options = default) { string language = null; diff --git a/.dotnet/src/Custom/Audio/AudioTranscriptionFormat.cs b/.dotnet/src/Custom/Audio/AudioTranscriptionFormat.cs index 585099b7a..73e283ae6 100644 --- a/.dotnet/src/Custom/Audio/AudioTranscriptionFormat.cs +++ b/.dotnet/src/Custom/Audio/AudioTranscriptionFormat.cs @@ -6,7 +6,7 @@ namespace OpenAI.Audio; public enum AudioTranscriptionFormat { Simple, - Detailed, + Verbose, Srt, Vtt, } \ No newline at end of file diff --git a/.dotnet/src/Custom/Audio/AudioTranscriptionOptions.cs b/.dotnet/src/Custom/Audio/AudioTranscriptionOptions.cs index 2b473f74d..e1a9e033e 100644 --- a/.dotnet/src/Custom/Audio/AudioTranscriptionOptions.cs +++ b/.dotnet/src/Custom/Audio/AudioTranscriptionOptions.cs @@ -1,14 +1,72 @@ +using OpenAI.Internal; using System; using System.Collections.Generic; +using System.IO; +using System.Text.Json; namespace OpenAI.Audio; public partial class AudioTranscriptionOptions { public string Language { get; set; } - public string Prompt { get; set; } + public string Prompt { get; set; } public AudioTranscriptionFormat? ResponseFormat { get; set; } public float? Temperature { get; set; } public bool? EnableWordTimestamps { get; set; } public bool? EnableSegmentTimestamps { get; set; } -} \ No newline at end of file + + internal MultipartFormDataBinaryContent ToMultipartContent(Stream file, string fileName, string model) + { + MultipartFormDataBinaryContent content = new(); + + content.Add(file, "file", fileName); + content.Add(model, "model"); + + if (Language is not null) + { + content.Add(Language, "language"); + } + + if (Prompt is not null) + { + content.Add(Prompt, "prompt"); + } + + if (ResponseFormat is not null) + { + string value = ResponseFormat switch + { + AudioTranscriptionFormat.Simple => "json", + AudioTranscriptionFormat.Verbose => "verbose_json", + AudioTranscriptionFormat.Srt => "srt", + AudioTranscriptionFormat.Vtt => "vtt", + _ => throw new ArgumentException(nameof(ResponseFormat)) + }; + + content.Add(value, "response_format"); + } + + if (Temperature is not null) + { + content.Add(Temperature.Value, "temperature"); + } + + if (EnableWordTimestamps is not null || EnableSegmentTimestamps is not null) + { + List granularities = []; + if (EnableWordTimestamps.Value) + { + granularities.Add("word"); + } + if (EnableSegmentTimestamps.Value) + { + granularities.Add("segment"); + } + + byte[] data = JsonSerializer.SerializeToUtf8Bytes(granularities); + content.Add(data, "timestamp_granularities"); + } + + return content; + } +} diff --git a/.dotnet/src/Custom/Audio/AudioTranslation.cs b/.dotnet/src/Custom/Audio/AudioTranslation.cs index 49de258bf..b683161b3 100644 --- a/.dotnet/src/Custom/Audio/AudioTranslation.cs +++ b/.dotnet/src/Custom/Audio/AudioTranslation.cs @@ -1,6 +1,5 @@ using System; using System.ClientModel.Primitives; -using System.Collections.Generic; using System.Text.Json; namespace OpenAI.Audio; @@ -14,7 +13,13 @@ internal AudioTranslation(string text) Text = text; } - internal static AudioTranslation DeserializeAudioTranscription(JsonElement element, ModelReaderWriterOptions options = default) + internal static AudioTranslation Deserialize(BinaryData content) + { + using JsonDocument responseDocument = JsonDocument.Parse(content); + return DeserializeAudioTranslation(responseDocument.RootElement); + } + + internal static AudioTranslation DeserializeAudioTranslation(JsonElement element, ModelReaderWriterOptions options = default) { string text = null; diff --git a/.dotnet/src/Custom/Audio/AudioTranslationOptions.cs b/.dotnet/src/Custom/Audio/AudioTranslationOptions.cs index 38e05a047..1e9fbbd55 100644 --- a/.dotnet/src/Custom/Audio/AudioTranslationOptions.cs +++ b/.dotnet/src/Custom/Audio/AudioTranslationOptions.cs @@ -1,5 +1,6 @@ +using OpenAI.Internal; using System; -using System.Collections.Generic; +using System.IO; namespace OpenAI.Audio; @@ -8,4 +9,33 @@ public partial class AudioTranslationOptions public string Prompt { get; set; } public AudioTranscriptionFormat? ResponseFormat { get; set; } public float? Temperature { get; set; } + + internal MultipartFormDataBinaryContent ToMultipartContent(Stream file, string fileName, string model) + { + MultipartFormDataBinaryContent content = new(); + + content.Add(file, "file", fileName); + content.Add(model, "model"); + + if (Prompt is not null) + { + content.Add(Prompt, "prompt"); + } + + if (ResponseFormat is not null) + { + string value = ResponseFormat switch + { + AudioTranscriptionFormat.Simple => "json", + AudioTranscriptionFormat.Verbose => "verbose_json", + AudioTranscriptionFormat.Srt => "srt", + AudioTranscriptionFormat.Vtt => "vtt", + _ => throw new ArgumentException(nameof(ResponseFormat)) + }; + + content.Add(value, "response_format"); + } + + return content; + } } \ No newline at end of file diff --git a/.dotnet/src/Custom/Audio/TranscribedWord.cs b/.dotnet/src/Custom/Audio/TranscribedWord.cs index 94e26fde3..dca3adee7 100644 --- a/.dotnet/src/Custom/Audio/TranscribedWord.cs +++ b/.dotnet/src/Custom/Audio/TranscribedWord.cs @@ -4,7 +4,7 @@ namespace OpenAI.Audio; -public partial class TranscribedWord +public readonly partial struct TranscribedWord { public string Word { get; } public TimeSpan Start { get; } diff --git a/.dotnet/src/Custom/Audio/TranscriptionSegment.cs b/.dotnet/src/Custom/Audio/TranscriptionSegment.cs index c1ee0632e..a40aba765 100644 --- a/.dotnet/src/Custom/Audio/TranscriptionSegment.cs +++ b/.dotnet/src/Custom/Audio/TranscriptionSegment.cs @@ -6,7 +6,7 @@ namespace OpenAI.Audio; -public partial class TranscriptionSegment +public readonly partial struct TranscriptionSegment { public int Id { get; } public int SeekOffset { get; } diff --git a/.dotnet/src/Custom/Chat/ChatClient.cs b/.dotnet/src/Custom/Chat/ChatClient.cs index fde5594d2..a594ae5eb 100644 --- a/.dotnet/src/Custom/Chat/ChatClient.cs +++ b/.dotnet/src/Custom/Chat/ChatClient.cs @@ -26,74 +26,14 @@ public partial class ChatClient /// if it is defined. /// /// - /// The connection endpoint to use. /// The model name for chat completions that the client should use. /// The API key used to authenticate with the service endpoint. /// Additional options to customize the client. - public ChatClient(Uri endpoint, string model, ApiKeyCredential credential, OpenAIClientOptions options = null) + public ChatClient(string model, ApiKeyCredential credential = default, OpenAIClientOptions options = null) { - _clientConnector = new(model, endpoint, credential, options); + _clientConnector = new(model, credential, options); } - /// - /// Initializes a new instance of , used for Chat Completion requests. - /// - /// - /// - /// If an endpoint is not provided, the client will use the OPENAI_ENDPOINT environment variable if it - /// defined and otherwise use the default OpenAI v1 endpoint. - /// - /// - /// If an authentication credential is not defined, the client use the OPENAI_API_KEY environment variable - /// if it is defined. - /// - /// - /// The connection endpoint to use. - /// The model name for chat completions that the client should use. - /// Additional options to customize the client. - public ChatClient(Uri endpoint, string model, OpenAIClientOptions options = null) - : this(endpoint, model, credential: null, options) - { } - - /// - /// Initializes a new instance of , used for Chat Completion requests. - /// - /// - /// - /// If an endpoint is not provided, the client will use the OPENAI_ENDPOINT environment variable if it - /// defined and otherwise use the default OpenAI v1 endpoint. - /// - /// - /// If an authentication credential is not defined, the client use the OPENAI_API_KEY environment variable - /// if it is defined. - /// - /// - /// The model name for chat completions that the client should use. - /// The API key used to authenticate with the service endpoint. - /// Additional options to customize the client. - public ChatClient(string model, ApiKeyCredential credential, OpenAIClientOptions options = null) - : this(endpoint: null, model, credential, options) - { } - - /// - /// Initializes a new instance of , used for Chat Completion requests. - /// - /// - /// - /// If an endpoint is not provided, the client will use the OPENAI_ENDPOINT environment variable if it - /// defined and otherwise use the default OpenAI v1 endpoint. - /// - /// - /// If an authentication credential is not defined, the client use the OPENAI_API_KEY environment variable - /// if it is defined. - /// - /// - /// The model name for chat completions that the client should use. - /// Additional options to customize the client. - public ChatClient(string model, OpenAIClientOptions options = null) - : this(endpoint: null, model, credential: null, options) - { } - /// /// Generates a single chat completion result for a single, simple user message. /// @@ -153,7 +93,6 @@ public virtual async Task> CompleteChatAsync( /// The number of independent, alternative response choices that should be generated. /// /// Additional options for the chat completion request. - /// The cancellation token for the operation. /// A result for a single chat completion. public virtual ClientResult CompleteChat( IEnumerable messages, @@ -251,29 +190,25 @@ public virtual Task> CompleteChatStre /// The number of independent, alternative choices that the chat completion request should generate. /// /// Additional options for the chat completion request. - /// The cancellation token for the operation. /// A streaming result with incremental chat completion updates. public virtual StreamingClientResult CompleteChatStreaming( IEnumerable messages, int? choiceCount = null, ChatCompletionOptions options = null) { - PipelineMessage requestMessage = CreateCustomRequestMessage(messages, choiceCount, options); - requestMessage.BufferResponse = false; - Shim.Pipeline.Send(requestMessage); - PipelineResponse response = requestMessage.ExtractResponse(); + PipelineMessage message = CreateCustomRequestMessage(messages, choiceCount, options); + message.BufferResponse = false; + + Shim.Pipeline.Send(message); + + PipelineResponse response = message.Response; if (response.IsError) { throw new ClientResultException(response); } - ClientResult genericResult = ClientResult.FromResponse(response); - return StreamingClientResult.CreateFromResponse( - genericResult, - (responseForEnumeration) => SseAsyncEnumerator.EnumerateFromSseJsonStream( - responseForEnumeration.GetRawResponse().ContentStream, - StreamingChatUpdate.DeserializeSseChatUpdates)); + return new StreamingChatResult(response); } /// @@ -289,29 +224,25 @@ public virtual StreamingClientResult CompleteChatStreaming( /// The number of independent, alternative choices that the chat completion request should generate. /// /// Additional options for the chat completion request. - /// The cancellation token for the operation. /// A streaming result with incremental chat completion updates. public virtual async Task> CompleteChatStreamingAsync( IEnumerable messages, int? choiceCount = null, ChatCompletionOptions options = null) { - PipelineMessage requestMessage = CreateCustomRequestMessage(messages, choiceCount, options); - requestMessage.BufferResponse = false; - await Shim.Pipeline.SendAsync(requestMessage).ConfigureAwait(false); - PipelineResponse response = requestMessage.ExtractResponse(); + PipelineMessage message = CreateCustomRequestMessage(messages, choiceCount, options); + message.BufferResponse = false; + + await Shim.Pipeline.SendAsync(message).ConfigureAwait(false); + + PipelineResponse response = message.Response; if (response.IsError) { throw new ClientResultException(response); } - ClientResult genericResult = ClientResult.FromResponse(response); - return StreamingClientResult.CreateFromResponse( - genericResult, - (responseForEnumeration) => SseAsyncEnumerator.EnumerateFromSseJsonStream( - responseForEnumeration.GetRawResponse().ContentStream, - StreamingChatUpdate.DeserializeSseChatUpdates)); + return new StreamingChatResult(response); } private Internal.Models.CreateChatCompletionRequest CreateInternalRequest( diff --git a/.dotnet/src/Custom/Chat/ChatFunctionCall.Serialization.cs b/.dotnet/src/Custom/Chat/ChatFunctionCall.Serialization.cs index eb198cfd4..8af52a6b0 100644 --- a/.dotnet/src/Custom/Chat/ChatFunctionCall.Serialization.cs +++ b/.dotnet/src/Custom/Chat/ChatFunctionCall.Serialization.cs @@ -13,7 +13,7 @@ public partial class ChatFunctionCall : IJsonModel void IJsonModel.Write(Utf8JsonWriter writer, ModelReaderWriterOptions options) { writer.WriteStartObject(); - writer.WriteString("name"u8, Name); + writer.WriteString("name"u8, FunctionName); writer.WriteString("arguments"u8, Arguments); writer.WriteEndObject(); } diff --git a/.dotnet/src/Custom/Chat/ChatFunctionCall.cs b/.dotnet/src/Custom/Chat/ChatFunctionCall.cs index 822f1577f..3504deeb8 100644 --- a/.dotnet/src/Custom/Chat/ChatFunctionCall.cs +++ b/.dotnet/src/Custom/Chat/ChatFunctionCall.cs @@ -24,7 +24,7 @@ public partial class ChatFunctionCall /// /// The name of the function being called by the model. /// - public required string Name { get; set; } + public required string FunctionName { get; set; } /// /// The arguments to the function being called by the model. /// @@ -41,7 +41,7 @@ public ChatFunctionCall() { } [SetsRequiredMembers] public ChatFunctionCall(string functionName, string arguments) { - Name = functionName; + FunctionName = functionName; Arguments = arguments; } } diff --git a/.dotnet/src/Custom/Chat/ChatMessageContent.cs b/.dotnet/src/Custom/Chat/ChatMessageContent.cs index a39fbf184..0a4b9d6d8 100644 --- a/.dotnet/src/Custom/Chat/ChatMessageContent.cs +++ b/.dotnet/src/Custom/Chat/ChatMessageContent.cs @@ -1,4 +1,5 @@ using System; +using System.IO; namespace OpenAI.Chat; @@ -28,7 +29,7 @@ internal ChatMessageContent(object value, ChatMessageContentKind kind, string c /// /// The content for the new instance. /// A new instance of . - public static ChatMessageContent CreateText(string text) => new(text, ChatMessageContentKind.Text); + public static ChatMessageContent FromText(string text) => new(text, ChatMessageContentKind.Text); /// /// Creates a new instance of that encapsulates image content obtained from @@ -38,16 +39,16 @@ internal ChatMessageContent(object value, ChatMessageContentKind kind, string c /// An internet location pointing to an image. This must be accessible to the model. /// /// A new instance of . - public static ChatMessageContent CreateImage(Uri imageUri) => new(imageUri, ChatMessageContentKind.Image); + public static ChatMessageContent FromImage(Uri imageUri) => new(imageUri, ChatMessageContentKind.Image); /// /// Creates a new instance of that encapsulates binary image content. /// - /// The binary representation of the image content. + /// The data stream containing the image content. /// The media type name, e.g. image/png, for the image. /// A new instance of . - public static ChatMessageContent CreateImage(BinaryData imageBytes, string mediaType) - => new(imageBytes, ChatMessageContentKind.Image, mediaType); + public static ChatMessageContent FromImage(Stream image, string mediaType) + => new(image, ChatMessageContentKind.Image, mediaType); /// /// Provides the associated with a content item using @@ -79,7 +80,7 @@ public Uri ToUri() ChatMessageContentKind.Image => _contentValue switch { Uri imageUri => imageUri, - BinaryData imageData => new Uri($"data:{_contentMediaTypeName};base64,{Convert.ToBase64String(imageData.ToArray())}"), + Stream imageStream => CreateDataUriFromStream(imageStream, _contentMediaTypeName), _ => throw new InvalidOperationException( $"Cannot convert underlying image data type '{_contentValue?.GetType()}' to a {nameof(Uri)}"), }, @@ -92,7 +93,7 @@ public Uri ToUri() /// a plain . /// /// The text for the message content. - public static implicit operator ChatMessageContent(string value) => CreateText(value); + public static implicit operator ChatMessageContent(string value) => FromText(value); /// /// An implicit operator allowing a content item to be treated as a string. @@ -109,4 +110,13 @@ public override string ToString() } return base.ToString(); } + + private static Uri CreateDataUriFromStream(Stream dataStream, string mediaType) + { + using MemoryStream byteStream = new(); + dataStream.CopyTo(byteStream); + byte[] bytes = byteStream.ToArray(); + string base64Bytes = Convert.ToBase64String(bytes); + return new Uri($"data:{mediaType};base64,{base64Bytes}"); + } } diff --git a/.dotnet/src/Custom/Chat/ChatRequestToolMessage.cs b/.dotnet/src/Custom/Chat/ChatRequestToolMessage.cs index 7ec17eea5..4208700ec 100644 --- a/.dotnet/src/Custom/Chat/ChatRequestToolMessage.cs +++ b/.dotnet/src/Custom/Chat/ChatRequestToolMessage.cs @@ -4,6 +4,7 @@ using System.ClientModel; using System.ClientModel.Primitives; using System.Text.Json; +using System.Diagnostics.CodeAnalysis; namespace OpenAI.Chat; @@ -29,7 +30,7 @@ public class ChatRequestToolMessage : ChatRequestMessage /// /// The id correlating to the prior made by the model. /// - public string ToolCallId { get; set; } + public required string ToolCallId { get; set; } /// /// Creates a new instance of . @@ -40,6 +41,7 @@ public class ChatRequestToolMessage : ChatRequestMessage /// that resolves the tool call and allows the logical conversation to continue. No format restrictions (e.g. /// JSON) are imposed on the content emitted by tools. /// + [SetsRequiredMembers] public ChatRequestToolMessage(string toolCallId, string content) : base(ChatRole.Tool, content) { diff --git a/.dotnet/src/Custom/Chat/ChatRequestUserMessage.cs b/.dotnet/src/Custom/Chat/ChatRequestUserMessage.cs index 95a80567a..79704394b 100644 --- a/.dotnet/src/Custom/Chat/ChatRequestUserMessage.cs +++ b/.dotnet/src/Custom/Chat/ChatRequestUserMessage.cs @@ -28,7 +28,7 @@ public class ChatRequestUserMessage : ChatRequestMessage /// /// The textual content associated with the message. public ChatRequestUserMessage(string content) - : base(ChatRole.User, ChatMessageContent.CreateText(content)) + : base(ChatRole.User, ChatMessageContent.FromText(content)) { } /// diff --git a/.dotnet/src/Custom/Chat/StreamingChatResult.cs b/.dotnet/src/Custom/Chat/StreamingChatResult.cs new file mode 100644 index 000000000..3b872e170 --- /dev/null +++ b/.dotnet/src/Custom/Chat/StreamingChatResult.cs @@ -0,0 +1,98 @@ +using System; +using System.ClientModel.Primitives; +using System.Collections.Generic; +using System.IO; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; + +namespace OpenAI.Chat; + +#nullable enable + +internal class StreamingChatResult : StreamingClientResult +{ + public StreamingChatResult(PipelineResponse response) : base(response) + { + } + + public override IAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken = default) + { + // Note: this implementation disposes the stream after the caller has + // enumerated the elements obtained from the stream. That is to say, + // the `await foreach` loop can only happen once -- if it is tried a + // second time, the caller will get an ObjectDisposedException trying + // to access a disposed Stream. + using PipelineResponse response = GetRawResponse(); + + // Extract the content stream from the response to obtain dispose + // ownership of it. This means the content stream will not be disposed + // when the response is disposed. + Stream contentStream = response.ContentStream ?? throw new InvalidOperationException("Cannot enumerate null response ContentStream."); + response.ContentStream = null; + + return new ChatUpdateEnumerator(contentStream); + } + + private class ChatUpdateEnumerator : IAsyncEnumerator + { + private readonly SseReader _sseReader; + + private List? _currentUpdates; + private int _currentUpdateIndex; + + public ChatUpdateEnumerator(Stream stream) + { + _sseReader = new(stream); + } + + public StreamingChatUpdate Current => throw new NotImplementedException(); + + public async ValueTask MoveNextAsync() + { + // TODO: How to handle the CancellationToken? + + if (_currentUpdates is not null && _currentUpdateIndex < _currentUpdates.Count) + { + _currentUpdateIndex++; + return true; + } + + // We either don't have any stored updates, or we've exceeded the + // count of the ones we have. Get the next set. + + // TODO: Call different configure await variant in this context, or no? + SseLine? sseEvent = await _sseReader.TryReadSingleFieldEventAsync().ConfigureAwait(false); + if (sseEvent is null) + { + // TODO: does this mean we're done or not? + return false; + } + + ReadOnlyMemory name = sseEvent.Value.FieldName; + if (!name.Span.SequenceEqual("data".AsSpan())) + { + throw new InvalidDataException(); + } + + ReadOnlyMemory value = sseEvent.Value.FieldValue; + if (value.Span.SequenceEqual("[DONE]".AsSpan())) + { + // enumerator semantics are that MoveNextAsync returns false when done. + return false; + } + + // TODO:optimize performance using Utf8JsonReader? + using JsonDocument sseMessageJson = JsonDocument.Parse(value); + _currentUpdates = StreamingChatUpdate.DeserializeSseChatUpdates(sseMessageJson.RootElement); + return true; + } + + public ValueTask DisposeAsync() + { + // TODO: revisit per platforms where async dispose is available. + _sseReader?.Dispose(); + return new ValueTask(); + } + } +} diff --git a/.dotnet/src/Custom/Chat/StreamingChatUpdate.cs b/.dotnet/src/Custom/Chat/StreamingChatUpdate.cs index f7e9e9ec4..1bbf19d5b 100644 --- a/.dotnet/src/Custom/Chat/StreamingChatUpdate.cs +++ b/.dotnet/src/Custom/Chat/StreamingChatUpdate.cs @@ -1,9 +1,9 @@ -namespace OpenAI.Chat; - using System; using System.Collections.Generic; using System.Text.Json; +namespace OpenAI.Chat; + /// /// Represents an incremental item of new data in a streaming response to a chat completion request. /// @@ -184,11 +184,16 @@ internal StreamingChatUpdate( internal static IEnumerable DeserializeSseChatUpdates(ReadOnlyMemory _, JsonElement sseDataJson) { + // TODO: Do we need to validate that we didn't get null or empty? + // What's the contract for the JSON updates? + List results = []; + if (sseDataJson.ValueKind == JsonValueKind.Null) { return results; } + string id = default; DateTimeOffset created = default; string systemFingerprint = null; diff --git a/.dotnet/src/Custom/Embeddings/EmbeddingClient.cs b/.dotnet/src/Custom/Embeddings/EmbeddingClient.cs index 441c64064..49a2e7b42 100644 --- a/.dotnet/src/Custom/Embeddings/EmbeddingClient.cs +++ b/.dotnet/src/Custom/Embeddings/EmbeddingClient.cs @@ -11,23 +11,11 @@ public partial class EmbeddingClient private readonly OpenAIClientConnector _clientConnector; private Internal.Embeddings Shim => _clientConnector.InternalClient.GetEmbeddingsClient(); - public EmbeddingClient(Uri endpoint, string model, ApiKeyCredential credential, OpenAIClientOptions options = null) + public EmbeddingClient(string model, ApiKeyCredential credential = default, OpenAIClientOptions options = default) { - _clientConnector = new(model, endpoint, credential, options); + _clientConnector = new(model, credential, options); } - public EmbeddingClient(Uri endpoint, string model, OpenAIClientOptions options = null) - : this(endpoint, model, credential: null, options) - { } - - public EmbeddingClient(string model, ApiKeyCredential credential, OpenAIClientOptions options = null) - : this(endpoint: null, model, credential, options) - { } - - public EmbeddingClient(string model, OpenAIClientOptions options = null) - : this(endpoint: null, model, credential: null, options) - { } - public virtual ClientResult GenerateEmbedding(string input, EmbeddingOptions options = null) { Internal.Models.CreateEmbeddingRequest request = CreateInternalRequest(input, options); diff --git a/.dotnet/src/Custom/Files/FileClient.Protocol.cs b/.dotnet/src/Custom/Files/FileClient.Protocol.cs index 263efec6c..d7298b1dc 100644 --- a/.dotnet/src/Custom/Files/FileClient.Protocol.cs +++ b/.dotnet/src/Custom/Files/FileClient.Protocol.cs @@ -1,12 +1,115 @@ -using System.ClientModel; +using System; +using System.ClientModel; using System.ClientModel.Primitives; using System.ComponentModel; +using System.IO; using System.Threading.Tasks; namespace OpenAI.Files; public partial class FileClient { + /// + /// [Protocol Method] Upload 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. + /// + /// The size of individual files can be a maximum of 512 MB or 2 million tokens for Assistants. See + /// the [Assistants Tools guide](/docs/assistants/tools) to learn more about the types of files + /// supported. The Fine-tuning API only supports `.jsonl` files. + /// + /// Please [contact us](https://help.openai.com/) if you need to increase these storage limits. + /// + /// + /// + /// This protocol method allows explicit creation of the request and processing of the response for advanced scenarios. + /// + /// + /// + /// + /// Please try the simpler or convenience overload with strongly typed models first. + /// + /// + /// + /// + /// The content to send as the body of the request. + /// The content type of the request. + /// The request options, which can override default behaviors of the client pipeline on a per-call basis. + /// is null. + /// is an empty string, and was expected to be non-empty. + /// Service returned a non-success status code. + /// The response returned from the service. + [EditorBrowsable(EditorBrowsableState.Never)] + public virtual ClientResult UploadFile(BinaryContent content, string contentType, RequestOptions options = null) + { + Argument.AssertNotNull(content, nameof(content)); + Argument.AssertNotNullOrEmpty(contentType, nameof(contentType)); + + options ??= new RequestOptions(); + + using PipelineMessage message = CreateUploadFileRequest(content, contentType, options); + + Shim.Pipeline.Send(message); + + PipelineResponse response = message.Response!; + + if (response.IsError && options.ErrorOptions == ClientErrorBehaviors.Default) + { + throw new ClientResultException(response); + } + + return ClientResult.FromResponse(response); + } + + /// + /// [Protocol Method] Upload 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. + /// + /// The size of individual files can be a maximum of 512 MB or 2 million tokens for Assistants. See + /// the [Assistants Tools guide](/docs/assistants/tools) to learn more about the types of files + /// supported. The Fine-tuning API only supports `.jsonl` files. + /// + /// Please [contact us](https://help.openai.com/) if you need to increase these storage limits. + /// + /// + /// + /// This protocol method allows explicit creation of the request and processing of the response for advanced scenarios. + /// + /// + /// + /// + /// Please try the simpler or convenience overload with strongly typed models first. + /// + /// + /// + /// + /// The content to send as the body of the request. + /// The content type of the request. + /// The request options, which can override default behaviors of the client pipeline on a per-call basis. + /// is null. + /// is an empty string, and was expected to be non-empty. + /// Service returned a non-success status code. + [EditorBrowsable(EditorBrowsableState.Never)] + public virtual async Task UploadFileAsync(BinaryContent content, string contentType, RequestOptions options = null) + { + Argument.AssertNotNull(content, nameof(content)); + Argument.AssertNotNullOrEmpty(contentType, nameof(contentType)); + + options ??= new RequestOptions(); + + using PipelineMessage message = CreateUploadFileRequest(content, contentType, options); + + Shim.Pipeline.Send(message); + + PipelineResponse response = message.Response!; + + if (response.IsError && options.ErrorOptions == ClientErrorBehaviors.Default) + { + throw await ClientResultException.CreateAsync(response).ConfigureAwait(false); + } + + return ClientResult.FromResponse(response); + } + /// [EditorBrowsable(EditorBrowsableState.Never)] public virtual ClientResult GetFileInfo(string fileId, RequestOptions options) diff --git a/.dotnet/src/Custom/Files/FileClient.cs b/.dotnet/src/Custom/Files/FileClient.cs index a1cdb9ea8..6dbeb8bf8 100644 --- a/.dotnet/src/Custom/Files/FileClient.cs +++ b/.dotnet/src/Custom/Files/FileClient.cs @@ -1,7 +1,9 @@ +using OpenAI.Internal; using System; using System.ClientModel; using System.ClientModel.Primitives; using System.Collections.Generic; +using System.IO; using System.Text; using System.Threading.Tasks; @@ -28,88 +30,51 @@ public partial class FileClient /// if it is defined. /// /// - /// The connection endpoint to use. /// The API key used to authenticate with the service endpoint. /// Additional options to customize the client. - public FileClient(Uri endpoint, ApiKeyCredential credential, OpenAIClientOptions options = null) + public FileClient(ApiKeyCredential credential = default, OpenAIClientOptions options = null) { - _clientConnector = new("none", endpoint, credential, options); + _clientConnector = new(model: null, credential, options); } - /// - /// Initializes a new instance of , used for file operation requests. - /// - /// - /// - /// If an endpoint is not provided, the client will use the OPENAI_ENDPOINT environment variable if it - /// defined and otherwise use the default OpenAI v1 endpoint. - /// - /// - /// If an authentication credential is not defined, the client use the OPENAI_API_KEY environment variable - /// if it is defined. - /// - /// - /// The connection endpoint to use. - /// Additional options to customize the client. - public FileClient(Uri endpoint, OpenAIClientOptions options = null) - : this(endpoint, credential: null, options) - { } + public virtual ClientResult UploadFile(FileStream file, OpenAIFilePurpose purpose) + => UploadFile(file, Path.GetFileName(file.Name), purpose); - /// - /// Initializes a new instance of , used for file operation requests. - /// - /// - /// - /// If an endpoint is not provided, the client will use the OPENAI_ENDPOINT environment variable if it - /// defined and otherwise use the default OpenAI v1 endpoint. - /// - /// - /// If an authentication credential is not defined, the client use the OPENAI_API_KEY environment variable - /// if it is defined. - /// - /// - /// The API key used to authenticate with the service endpoint. - /// Additional options to customize the client. - public FileClient(ApiKeyCredential credential, OpenAIClientOptions options = null) - : this(endpoint: null, credential, options) - { } + public virtual ClientResult UploadFile(Stream file, string fileName, OpenAIFilePurpose purpose) + { + Argument.AssertNotNull(file, nameof(file)); + Argument.AssertNotNull(fileName, nameof(fileName)); - /// - /// Initializes a new instance of , used for file operation requests. - /// - /// - /// - /// If an endpoint is not provided, the client will use the OPENAI_ENDPOINT environment variable if it - /// defined and otherwise use the default OpenAI v1 endpoint. - /// - /// - /// If an authentication credential is not defined, the client use the OPENAI_API_KEY environment variable - /// if it is defined. - /// - /// - /// Additional options to customize the client. - public FileClient(OpenAIClientOptions options = null) - : this(endpoint: null, credential: null, options) - { } + using MultipartFormDataBinaryContent content = UploadFileOptions.ToMultipartContent(file, fileName, purpose); - public virtual ClientResult UploadFile(BinaryData file, string filename, OpenAIFilePurpose purpose) - { - if (file is null) throw new ArgumentNullException(nameof(file)); - if (string.IsNullOrWhiteSpace(filename)) throw new ArgumentException(nameof(filename)); + ClientResult result = UploadFile(content, content.ContentType); + + PipelineResponse response = result.GetRawResponse(); - PipelineMessage uploadMessage = CreateInternalUploadMessage(file, filename, purpose); - Shim.Pipeline.Send(uploadMessage); - return GetUploadResultFromResponse(uploadMessage.Response); + Internal.Models.OpenAIFile internalFile = Internal.Models.OpenAIFile.FromResponse(response); + OpenAIFileInfo fileInfo = new(internalFile); + + return ClientResult.FromValue(fileInfo, response); } - public virtual async Task> UploadFileAsync(BinaryData file, string filename, OpenAIFilePurpose purpose) + public virtual async Task> UploadFileAsync(FileStream file, OpenAIFilePurpose purpose) + => await UploadFileAsync(file, Path.GetFileName(file.Name), purpose).ConfigureAwait(false); + + public virtual async Task> UploadFileAsync(Stream file, string fileName, OpenAIFilePurpose purpose) { - if (file is null) throw new ArgumentNullException(nameof(file)); - if (string.IsNullOrWhiteSpace(filename)) throw new ArgumentException(nameof(filename)); + Argument.AssertNotNull(file, nameof(file)); + Argument.AssertNotNull(fileName, nameof(fileName)); + + using MultipartFormDataBinaryContent content = UploadFileOptions.ToMultipartContent(file, fileName, purpose); + + ClientResult result = await UploadFileAsync(content, content.ContentType).ConfigureAwait(false); + + PipelineResponse response = result.GetRawResponse(); + + Internal.Models.OpenAIFile internalFile = Internal.Models.OpenAIFile.FromResponse(response); + OpenAIFileInfo fileInfo = new(internalFile); - PipelineMessage uploadMessage = CreateInternalUploadMessage(file, filename, purpose); - await Shim.Pipeline.SendAsync(uploadMessage).ConfigureAwait(false); - return GetUploadResultFromResponse(uploadMessage.Response); + return ClientResult.FromValue(fileInfo, response); } public virtual ClientResult GetFileInfo(string fileId) @@ -213,53 +178,33 @@ public virtual async Task DeleteFileAsync(string fileId) _ = Shim.DeleteFileAsync(fileId); } - internal PipelineMessage CreateInternalUploadMessage(BinaryData fileData, string filename, OpenAIFilePurpose purpose) + private PipelineMessage CreateUploadFileRequest(BinaryContent content, string contentType, RequestOptions options) { - MultipartFormDataContent content = new(); - content.Add(BinaryContent.Create(fileData), - name: "file", - fileName: filename, - headers: []); - content.Add(MultipartContent.Create( - BinaryData.FromString(purpose switch - { - OpenAIFilePurpose.FineTuning => "fine-tune", - OpenAIFilePurpose.Assistants => "assistants", - _ => throw new ArgumentException($"Unsupported purpose for file upload: {purpose}"), - })), - name: "\"purpose\"", - headers: []); - PipelineMessage message = Shim.Pipeline.CreateMessage(); message.ResponseClassifier = ResponseErrorClassifier200; + PipelineRequest request = message.Request; request.Method = "POST"; + UriBuilder uriBuilder = new(_clientConnector.Endpoint.AbsoluteUri); + StringBuilder path = new(); path.Append("/files"); uriBuilder.Path += path.ToString(); + request.Uri = uriBuilder.Uri; + request.Headers.Set("Accept", "application/json"); + request.Headers.Set("Content-Type", contentType); + request.Content = content; - content.ApplyToRequest(request); + message.Apply(options); return message; } - internal ClientResult GetUploadResultFromResponse(PipelineResponse response) - { - if (response.IsError) - { - throw new ClientResultException(response); - } - - Internal.Models.OpenAIFile internalFile = Internal.Models.OpenAIFile.FromResponse(response); - OpenAIFileInfo fileInfo = new(internalFile); - return ClientResult.FromValue(fileInfo, response); - } - - internal static Internal.Models.OpenAIFilePurpose? ToInternalFilePurpose(OpenAIFilePurpose? purpose) + private static Internal.Models.OpenAIFilePurpose? ToInternalFilePurpose(OpenAIFilePurpose? purpose) { if (purpose == null) { @@ -274,7 +219,7 @@ internal ClientResult GetUploadResultFromResponse(PipelineRespon _ => throw new ArgumentException($"Unsupported file purpose: {purpose}"), }; } + private static PipelineMessageClassifier _responseErrorClassifier200; private static PipelineMessageClassifier ResponseErrorClassifier200 => _responseErrorClassifier200 ??= PipelineMessageClassifier.Create(stackalloc ushort[] { 200 }); - } diff --git a/.dotnet/src/Custom/Files/UploadFileOptions.cs b/.dotnet/src/Custom/Files/UploadFileOptions.cs new file mode 100644 index 000000000..bef736831 --- /dev/null +++ b/.dotnet/src/Custom/Files/UploadFileOptions.cs @@ -0,0 +1,26 @@ +using OpenAI.Internal; +using System; +using System.IO; + +namespace OpenAI.Files; + +internal class UploadFileOptions +{ + internal static MultipartFormDataBinaryContent ToMultipartContent(Stream file, string fileName, OpenAIFilePurpose purpose) + { + MultipartFormDataBinaryContent content = new(); + + content.Add(file, "file", fileName); + + string purposeValue = purpose switch + { + OpenAIFilePurpose.FineTuning => "fine-tune", + OpenAIFilePurpose.Assistants => "assistants", + _ => throw new ArgumentException($"Unsupported purpose for file upload: {purpose}"), + }; + + content.Add(purposeValue, "\"purpose\""); + + return content; + } +} diff --git a/.dotnet/src/Custom/FineTuning/FineTuningManagementClient.cs b/.dotnet/src/Custom/FineTuning/FineTuningManagementClient.cs index 7bb1a1220..ad12cd2e5 100644 --- a/.dotnet/src/Custom/FineTuning/FineTuningManagementClient.cs +++ b/.dotnet/src/Custom/FineTuning/FineTuningManagementClient.cs @@ -12,7 +12,7 @@ public partial class FineTuningManagementClient private Internal.FineTuning FineTuningShim => _clientConnector.InternalClient.GetFineTuningClient(); /// - /// Initializes a new instance of , used for fine-tuning operation requests. + /// Initializes a new instance of , used for fine-tuning operation requests. /// /// /// @@ -24,67 +24,10 @@ public partial class FineTuningManagementClient /// if it is defined. /// /// - /// The connection endpoint to use. /// The API key used to authenticate with the service endpoint. /// Additional options to customize the client. - public FineTuningManagementClient(Uri endpoint, ApiKeyCredential credential, OpenAIClientOptions options = null) + public FineTuningManagementClient(ApiKeyCredential credential = default, OpenAIClientOptions options = default) { - _clientConnector = new("none", endpoint, credential, options); + _clientConnector = new(model: null, credential, options); } - - /// - /// Initializes a new instance of , used for fine-tuning operation requests. - /// - /// - /// - /// If an endpoint is not provided, the client will use the OPENAI_ENDPOINT environment variable if it - /// defined and otherwise use the default OpenAI v1 endpoint. - /// - /// - /// If an authentication credential is not defined, the client use the OPENAI_API_KEY environment variable - /// if it is defined. - /// - /// - /// The connection endpoint to use. - /// Additional options to customize the client. - public FineTuningManagementClient(Uri endpoint, OpenAIClientOptions options = null) - : this(endpoint, credential: null, options) - { } - - /// - /// Initializes a new instance of , used for fine-tuning operation requests. - /// - /// - /// - /// If an endpoint is not provided, the client will use the OPENAI_ENDPOINT environment variable if it - /// defined and otherwise use the default OpenAI v1 endpoint. - /// - /// - /// If an authentication credential is not defined, the client use the OPENAI_API_KEY environment variable - /// if it is defined. - /// - /// - /// The API key used to authenticate with the service endpoint. - /// Additional options to customize the client. - public FineTuningManagementClient(ApiKeyCredential credential, OpenAIClientOptions options = null) - : this(endpoint: null, credential, options) - { } - - /// - /// Initializes a new instance of , used for fine-tuning operation requests. - /// - /// - /// - /// If an endpoint is not provided, the client will use the OPENAI_ENDPOINT environment variable if it - /// defined and otherwise use the default OpenAI v1 endpoint. - /// - /// - /// If an authentication credential is not defined, the client use the OPENAI_API_KEY environment variable - /// if it is defined. - /// - /// - /// Additional options to customize the client. - public FineTuningManagementClient(OpenAIClientOptions options = null) - : this(endpoint: null, credential: null, options) - { } } diff --git a/.dotnet/src/Custom/Images/GeneratedImageCollection.cs b/.dotnet/src/Custom/Images/GeneratedImageCollection.cs index 78d217f1a..9f286e260 100644 --- a/.dotnet/src/Custom/Images/GeneratedImageCollection.cs +++ b/.dotnet/src/Custom/Images/GeneratedImageCollection.cs @@ -1,5 +1,7 @@ +using System; using System.Collections.Generic; using System.Collections.ObjectModel; +using System.Text.Json; namespace OpenAI.Images; @@ -9,4 +11,23 @@ namespace OpenAI.Images; public class GeneratedImageCollection : ReadOnlyCollection { internal GeneratedImageCollection(IList list) : base(list) { } + + internal static GeneratedImageCollection Deserialize(BinaryData content) + { + using JsonDocument responseDocument = JsonDocument.Parse(content); + return Deserialize(responseDocument.RootElement); + } + + internal static GeneratedImageCollection Deserialize(JsonElement element) + { + Internal.Models.ImagesResponse response = Internal.Models.ImagesResponse.DeserializeImagesResponse(element); + + List images = []; + for (int i = 0; i < response.Data.Count; i++) + { + images.Add(new GeneratedImage(response, i)); + } + + return new GeneratedImageCollection(images); + } } \ No newline at end of file diff --git a/.dotnet/src/Custom/Images/GeneratedImageSize.cs b/.dotnet/src/Custom/Images/GeneratedImageSize.cs new file mode 100644 index 000000000..3ec49aed5 --- /dev/null +++ b/.dotnet/src/Custom/Images/GeneratedImageSize.cs @@ -0,0 +1,86 @@ +namespace OpenAI.Images; + +/// +/// Represents the available output dimensions for generated images. +/// +public partial class GeneratedImageSize +{ + /// + /// Gets the desired width, in pixels, for an image. + /// + public int Width { get; } + + /// + /// Gets the desired height, in pixels, for an image. + /// + public int Height { get; } + + /// + /// Creates a new instance of . + /// + /// + /// Note: arbitrary dimensions are not supported and a given model will only support a set of predefined + /// sizes. If supported dimensions are not known, try using one of the static properties like . + /// + /// The desired width, in pixels, for an image. + /// The desired height, in pixels, for an image. + public GeneratedImageSize(int width, int height) + { + Width = width; + Height = height; + } + + /// + /// A small, square image with 256 pixels of both width and height. + /// + /// Supported only for the older dall-e-2 model. + /// + /// + public static readonly GeneratedImageSize W256xH256 = new(256, 256); + + /// + /// A medium-small, square image with 512 pixels of both width and height. + /// + /// Supported only for the older dall-e-2 model. + /// + /// + public static readonly GeneratedImageSize W512xH512 = new(512, 512); + + /// + /// A square image with 1024 pixels of both width and height. + /// + /// Supported and default for both dall-e-2 and dall-e-3 models. + /// + /// + public static readonly GeneratedImageSize W1024xH1024 = new(1024, 1024); + + /// + /// An extra tall image, 1024 pixels wide by 1792 pixels high. + /// + /// Supported only for the dall-e-3 model. + /// + /// + public static readonly GeneratedImageSize W1024xH1792 = new(1024, 1792); + + /// + /// An extra wide image, 1792 pixels wide by 1024 pixels high. + /// + /// Supported only for the dall-e-3 model. + /// + /// + public static readonly GeneratedImageSize W1792xH1024 = new(1792, 1024); + + /// + public static bool operator ==(GeneratedImageSize left, GeneratedImageSize right) + => ((left is null) == (right is null)) && (left is null || left.Equals(right)); + + /// + public static bool operator !=(GeneratedImageSize left, GeneratedImageSize right) + => ((left is null) != (right is null)) || (left is not null && !left.Equals(right)); + + /// + public bool Equals(GeneratedImageSize other) => other?.Width == Width && other?.Height == Height; + + /// + public override bool Equals(object obj) => obj is GeneratedImageSize other && Equals(other); +} \ No newline at end of file diff --git a/.dotnet/src/Custom/Images/ImageClient.Protocol.cs b/.dotnet/src/Custom/Images/ImageClient.Protocol.cs index b95c9b4d0..61fc82eb1 100644 --- a/.dotnet/src/Custom/Images/ImageClient.Protocol.cs +++ b/.dotnet/src/Custom/Images/ImageClient.Protocol.cs @@ -1,6 +1,8 @@ -using System.ClientModel.Primitives; +using System; +using System.ClientModel.Primitives; using System.ClientModel; using System.ComponentModel; +using System.IO; using System.Threading.Tasks; namespace OpenAI.Images; @@ -16,4 +18,180 @@ public virtual ClientResult GenerateImage(BinaryContent content, RequestOptions [EditorBrowsable(EditorBrowsableState.Never)] public virtual async Task GenerateImageAsync(BinaryContent content, RequestOptions options = null) => await Shim.CreateImageAsync(content, options).ConfigureAwait(false); + + /// + /// [Protocol Method] Creates an edited or extended image given an original image and a prompt. + /// + /// + /// + /// This protocol method allows explicit creation of the request and processing of the response for advanced scenarios. + /// + /// + /// + /// + /// Please try the simpler or convenience overload with strongly typed models first. + /// + /// + /// + /// + /// The content to send as the body of the request. + /// The content type of the request. + /// The request options, which can override default behaviors of the client pipeline on a per-call basis. + /// is null. + /// is an empty string, and was expected to be non-empty. + /// Service returned a non-success status code. + /// The response returned from the service. + [EditorBrowsable(EditorBrowsableState.Never)] + public virtual ClientResult GenerateImageEdits(BinaryContent content, string contentType, RequestOptions options = null) + { + Argument.AssertNotNull(content, nameof(content)); + Argument.AssertNotNullOrEmpty(contentType, nameof(contentType)); + + options ??= new RequestOptions(); + + using PipelineMessage message = CreateCreateImageEditsRequest(content, contentType, options); + + Shim.Pipeline.Send(message); + + PipelineResponse response = message.Response!; + + if (response.IsError && options.ErrorOptions == ClientErrorBehaviors.Default) + { + throw new ClientResultException(response); + } + + return ClientResult.FromResponse(response); + } + + /// + /// [Protocol Method] Creates an edited or extended image given an original image and a prompt. + /// + /// + /// + /// This protocol method allows explicit creation of the request and processing of the response for advanced scenarios. + /// + /// + /// + /// + /// Please try the simpler or convenience overload with strongly typed models first. + /// + /// + /// + /// + /// The content to send as the body of the request. + /// The content type of the request. + /// The request options, which can override default behaviors of the client pipeline on a per-call basis. + /// is null. + /// is an empty string, and was expected to be non-empty. + /// Service returned a non-success status code. + /// The response returned from the service. + [EditorBrowsable(EditorBrowsableState.Never)] + public virtual async Task GenerateImageEditsAsync(BinaryContent content, string contentType, RequestOptions options = null) + { + Argument.AssertNotNull(content, nameof(content)); + Argument.AssertNotNullOrEmpty(contentType, nameof(contentType)); + + options ??= new RequestOptions(); + + using PipelineMessage message = CreateCreateImageEditsRequest(content, contentType, options); + + Shim.Pipeline.Send(message); + + PipelineResponse response = message.Response!; + + if (response.IsError && options.ErrorOptions == ClientErrorBehaviors.Default) + { + throw await ClientResultException.CreateAsync(response).ConfigureAwait(false); + } + + return ClientResult.FromResponse(response); + } + + /// + /// [Protocol Method] Creates an edited or extended image given an original image and a prompt. + /// + /// + /// + /// This protocol method allows explicit creation of the request and processing of the response for advanced scenarios. + /// + /// + /// + /// + /// Please try the simpler or convenience overload with strongly typed models first. + /// + /// + /// + /// + /// The content to send as the body of the request. + /// The content type of the request. + /// The request options, which can override default behaviors of the client pipeline on a per-call basis. + /// is null. + /// is an empty string, and was expected to be non-empty. + /// Service returned a non-success status code. + /// The response returned from the service. + [EditorBrowsable(EditorBrowsableState.Never)] + public virtual ClientResult GenerateImageVariations(BinaryContent content, string contentType, RequestOptions options = null) + { + Argument.AssertNotNull(content, nameof(content)); + Argument.AssertNotNullOrEmpty(contentType, nameof(contentType)); + + options ??= new RequestOptions(); + + using PipelineMessage message = CreateImageVariationsRequest(content, contentType, options); + + Shim.Pipeline.Send(message); + + PipelineResponse response = message.Response!; + + if (response.IsError && options.ErrorOptions == ClientErrorBehaviors.Default) + { + throw new ClientResultException(response); + } + + return ClientResult.FromResponse(response); + } + + /// + /// [Protocol Method] Creates an edited or extended image given an original image and a prompt. + /// + /// + /// + /// This protocol method allows explicit creation of the request and processing of the response for advanced scenarios. + /// + /// + /// + /// + /// Please try the simpler or convenience overload with strongly typed models first. + /// + /// + /// + /// + /// The content to send as the body of the request. + /// The content type of the request. + /// The request options, which can override default behaviors of the client pipeline on a per-call basis. + /// is null. + /// is an empty string, and was expected to be non-empty. + /// Service returned a non-success status code. + /// The response returned from the service. + [EditorBrowsable(EditorBrowsableState.Never)] + public virtual async Task GenerateImageVariationsAsync(BinaryContent content, string contentType, RequestOptions options = null) + { + Argument.AssertNotNull(content, nameof(content)); + Argument.AssertNotNullOrEmpty(contentType, nameof(contentType)); + + options ??= new RequestOptions(); + + using PipelineMessage message = CreateImageVariationsRequest(content, contentType, options); + + Shim.Pipeline.Send(message); + + PipelineResponse response = message.Response!; + + if (response.IsError && options.ErrorOptions == ClientErrorBehaviors.Default) + { + throw await ClientResultException.CreateAsync(response).ConfigureAwait(false); + } + + return ClientResult.FromResponse(response); + } } diff --git a/.dotnet/src/Custom/Images/ImageClient.cs b/.dotnet/src/Custom/Images/ImageClient.cs index 5e29e045b..62ddb7339 100644 --- a/.dotnet/src/Custom/Images/ImageClient.cs +++ b/.dotnet/src/Custom/Images/ImageClient.cs @@ -1,12 +1,10 @@ -using OpenAI.Audio; -using OpenAI.ClientShared.Internal; +using OpenAI.Internal; using System; using System.ClientModel; using System.ClientModel.Primitives; using System.Collections.Generic; -using System.Runtime.InteropServices.ComTypes; +using System.IO; using System.Text; -using System.Text.Json; using System.Threading.Tasks; namespace OpenAI.Images; @@ -30,80 +28,19 @@ public partial class ImageClient /// if it is defined. /// /// - /// The connection endpoint to use. /// The model name for image operations that the client should use. /// The API key used to authenticate with the service endpoint. /// Additional options to customize the client. - public ImageClient(Uri endpoint, string model, ApiKeyCredential credential, OpenAIClientOptions options = null) + public ImageClient(string model, ApiKeyCredential credential = default, OpenAIClientOptions options = null) { - _clientConnector = new(model, endpoint, credential, options); + _clientConnector = new(model, credential, options); } - /// - /// Initializes a new instance of , used for image operation requests. - /// - /// - /// - /// If an endpoint is not provided, the client will use the OPENAI_ENDPOINT environment variable if it - /// defined and otherwise use the default OpenAI v1 endpoint. - /// - /// - /// If an authentication credential is not defined, the client use the OPENAI_API_KEY environment variable - /// if it is defined. - /// - /// - /// The connection endpoint to use. - /// The model name for image operations that the client should use. - /// Additional options to customize the client. - public ImageClient(Uri endpoint, string model, OpenAIClientOptions options = null) - : this(endpoint, model, credential: null, options) - { } - - /// - /// Initializes a new instance of , used for image operation requests. - /// - /// - /// - /// If an endpoint is not provided, the client will use the OPENAI_ENDPOINT environment variable if it - /// defined and otherwise use the default OpenAI v1 endpoint. - /// - /// - /// If an authentication credential is not defined, the client use the OPENAI_API_KEY environment variable - /// if it is defined. - /// - /// - /// The model name for image operations that the client should use. - /// The API key used to authenticate with the service endpoint. - /// Additional options to customize the client. - public ImageClient(string model, ApiKeyCredential credential, OpenAIClientOptions options = null) - : this(endpoint: null, model, credential, options) - { } - - /// - /// Initializes a new instance of , used for image operation requests. - /// - /// - /// - /// If an endpoint is not provided, the client will use the OPENAI_ENDPOINT environment variable if it - /// defined and otherwise use the default OpenAI v1 endpoint. - /// - /// - /// If an authentication credential is not defined, the client use the OPENAI_API_KEY environment variable - /// if it is defined. - /// - /// - /// The model name for image operations that the client should use. - /// Additional options to customize the client. - public ImageClient(string model, OpenAIClientOptions options = null) - : this(endpoint: null, model, credential: null, options) - { } - /// /// Generates a single image for a provided prompt. /// /// The description and instructions for the image. /// Additional options for the image generation request. - /// The cancellation token for the operation. /// A result for a single image generation. public virtual ClientResult GenerateImage( string prompt, @@ -118,7 +55,6 @@ public virtual ClientResult GenerateImage( /// /// The description and instructions for the image. /// Additional options for the image generation request. - /// The cancellation token for the operation. /// A result for a single image generation. public virtual async Task> GenerateImageAsync( string prompt, @@ -136,7 +72,6 @@ public virtual async Task> GenerateImageAsync( /// The number of alternative images to generate for the prompt. /// /// Additional options for the image generation request. - /// The cancellation token for the operation. /// A result for a single image generation. public virtual ClientResult GenerateImages( string prompt, @@ -163,7 +98,6 @@ public virtual ClientResult GenerateImages( /// The number of alternative images to generate for the prompt. /// /// Additional options for the image generation request. - /// The cancellation token for the operation. /// A result for a single image generation. public virtual async Task> GenerateImagesAsync( string prompt, @@ -172,7 +106,7 @@ public virtual async Task> GenerateImages { Internal.Models.CreateImageRequest request = CreateInternalImageRequest(prompt, imageCount, options); ClientResult response = await Shim.CreateImageAsync(request).ConfigureAwait(false); - + List images = []; for (int i = 0; i < response.Value.Data.Count; i++) { @@ -183,105 +117,132 @@ public virtual async Task> GenerateImages } public virtual ClientResult GenerateImageEdits( - BinaryData imageBytes, + FileStream image, + string prompt, + int? imageCount = null, + ImageEditOptions options = null) + => GenerateImageEdits(image, Path.GetFileName(image.Name), prompt, imageCount, options); + + public virtual ClientResult GenerateImageEdits( + Stream image, + string fileName, string prompt, int? imageCount = null, ImageEditOptions options = null) { - PipelineMessage message = CreateInternalImageEditsPipelineMessage(imageBytes, prompt, imageCount, options); - Shim.Pipeline.Send(message); + Argument.AssertNotNull(image, nameof(image)); + Argument.AssertNotNull(fileName, nameof(fileName)); + Argument.AssertNotNull(prompt, nameof(prompt)); - if (message.Response.IsError) + if (options?.Mask is not null) { - throw new ClientResultException(message.Response); + Argument.AssertNotNull(options.MaskFileName, nameof(options.MaskFileName)); } - using JsonDocument responseDocument = JsonDocument.Parse(message.Response.Content); - Internal.Models.ImagesResponse response = Internal.Models.ImagesResponse.DeserializeImagesResponse(responseDocument.RootElement); + options ??= new(); - List images = []; - for (int i = 0; i < response.Data.Count; i++) - { - images.Add(new GeneratedImage(response, i)); - } + using MultipartFormDataBinaryContent content = options.ToMultipartContent(image, fileName, prompt, _clientConnector.Model, imageCount); - return ClientResult.FromValue(new GeneratedImageCollection(images), message.Response); + ClientResult result = GenerateImageEdits(content, content.ContentType); + + PipelineResponse response = result.GetRawResponse(); + + GeneratedImageCollection value = GeneratedImageCollection.Deserialize(response.Content!); + + return ClientResult.FromValue(value, response); } public virtual async Task> GenerateImageEditsAsync( - BinaryData imageBytes, + FileStream image, + string prompt, + int? imageCount = null, + ImageEditOptions options = null) + => await GenerateImageEditsAsync(image, Path.GetFileName(image.Name), prompt, imageCount, options).ConfigureAwait(false); + + public virtual async Task> GenerateImageEditsAsync( + Stream image, + string fileName, string prompt, int? imageCount = null, ImageEditOptions options = null) { - PipelineMessage message = CreateInternalImageEditsPipelineMessage(imageBytes, prompt, imageCount, options); - await Shim.Pipeline.SendAsync(message).ConfigureAwait(false); + Argument.AssertNotNull(image, nameof(image)); + Argument.AssertNotNull(fileName, nameof(fileName)); + Argument.AssertNotNull(prompt, nameof(prompt)); - if (message.Response.IsError) + if (options?.Mask is not null) { - throw new ClientResultException(message.Response); + Argument.AssertNotNull(options.MaskFileName, nameof(options.MaskFileName)); } - using JsonDocument responseDocument = JsonDocument.Parse(message.Response.Content); - Internal.Models.ImagesResponse response = Internal.Models.ImagesResponse.DeserializeImagesResponse(responseDocument.RootElement); + options ??= new(); - List images = []; - for (int i = 0; i < response.Data.Count; i++) - { - images.Add(new GeneratedImage(response, i)); - } + using MultipartFormDataBinaryContent content = options.ToMultipartContent(image, fileName, prompt, _clientConnector.Model, imageCount); - return ClientResult.FromValue(new GeneratedImageCollection(images), message.Response); + ClientResult result = await GenerateImageEditsAsync(content, content.ContentType).ConfigureAwait(false); + + PipelineResponse response = result.GetRawResponse(); + + GeneratedImageCollection value = GeneratedImageCollection.Deserialize(response.Content!); + + return ClientResult.FromValue(value, response); } public virtual ClientResult GenerateImageVariations( - BinaryData imageBytes, + FileStream image, + int? imageCount = null, + ImageVariationOptions options = null) + => GenerateImageVariations(image, Path.GetFileName(image.Name), imageCount, options); + + public virtual ClientResult GenerateImageVariations( + Stream image, + string fileName, int? imageCount = null, ImageVariationOptions options = null) { - PipelineMessage message = CreateInternalImageVariationsPipelineMessage(imageBytes, imageCount, options); - Shim.Pipeline.Send(message); + Argument.AssertNotNull(image, nameof(image)); + Argument.AssertNotNull(fileName, nameof(fileName)); - if (message.Response.IsError) - { - throw new ClientResultException(message.Response); - } + options ??= new(); - using JsonDocument responseDocument = JsonDocument.Parse(message.Response.Content); - Internal.Models.ImagesResponse response = Internal.Models.ImagesResponse.DeserializeImagesResponse(responseDocument.RootElement); + using MultipartFormDataBinaryContent content = options.ToMultipartContent(image, fileName, _clientConnector.Model, imageCount); - List images = []; - for (int i = 0; i < response.Data.Count; i++) - { - images.Add(new GeneratedImage(response, i)); - } + ClientResult result = GenerateImageVariations(content, content.ContentType); + + PipelineResponse response = result.GetRawResponse(); + + GeneratedImageCollection value = GeneratedImageCollection.Deserialize(response.Content!); - return ClientResult.FromValue(new GeneratedImageCollection(images), message.Response); + return ClientResult.FromValue(value, response); } + public virtual async Task> GenerateImageVariationsAsync( - BinaryData imageBytes, + FileStream image, + int? imageCount = null, + ImageVariationOptions options = null) + => await GenerateImageVariationsAsync(image, Path.GetFileName(image.Name), imageCount, options).ConfigureAwait(false); + + public virtual async Task> GenerateImageVariationsAsync( + Stream image, + string fileName, int? imageCount = null, ImageVariationOptions options = null) { - PipelineMessage message = CreateInternalImageVariationsPipelineMessage(imageBytes, imageCount, options); - await Shim.Pipeline.SendAsync(message).ConfigureAwait(false); + Argument.AssertNotNull(image, nameof(image)); + Argument.AssertNotNull(fileName, nameof(fileName)); - if (message.Response.IsError) - { - throw new ClientResultException(message.Response); - } + options ??= new(); - using JsonDocument responseDocument = JsonDocument.Parse(message.Response.Content); - Internal.Models.ImagesResponse response = Internal.Models.ImagesResponse.DeserializeImagesResponse(responseDocument.RootElement); + using MultipartFormDataBinaryContent content = options.ToMultipartContent(image, fileName, _clientConnector.Model, imageCount); - List images = []; - for (int i = 0; i < response.Data.Count; i++) - { - images.Add(new GeneratedImage(response, i)); - } + ClientResult result = await GenerateImageVariationsAsync(content, content.ContentType).ConfigureAwait(false); - return ClientResult.FromValue(new GeneratedImageCollection(images), message.Response); + PipelineResponse response = result.GetRawResponse(); + + GeneratedImageCollection value = GeneratedImageCollection.Deserialize(response.Content!); + + return ClientResult.FromValue(value, response); } private Internal.Models.CreateImageRequest CreateInternalImageRequest( @@ -315,15 +276,7 @@ private Internal.Models.CreateImageRequest CreateInternalImageRequest( Internal.Models.CreateImageRequestSize? internalSize = null; if (options.Size != null) { - internalSize = options.Size switch - { - ImageSize.Size256x256 => Internal.Models.CreateImageRequestSize._256x256, - ImageSize.Size512x512 => Internal.Models.CreateImageRequestSize._512x512, - ImageSize.Size1024x1024 => Internal.Models.CreateImageRequestSize._1024x1024, - ImageSize.Size1024x1792 => Internal.Models.CreateImageRequestSize._1024x1792, - ImageSize.Size1792x1024 => Internal.Models.CreateImageRequestSize._1792x1024, - _ => throw new ArgumentException(nameof(options.Size)), - }; + internalSize = ModelReaderWriter.Write(options.Size).ToString(); } Internal.Models.CreateImageRequestStyle? internalStyle = null; @@ -349,240 +302,56 @@ private Internal.Models.CreateImageRequest CreateInternalImageRequest( serializedAdditionalRawData: null); } - private PipelineMessage CreateInternalImageEditsPipelineMessage( - BinaryData imageBytes, - string prompt, - int? imageCount = null, - ImageEditOptions options = null) + private PipelineMessage CreateCreateImageEditsRequest(BinaryContent content, string contentType, RequestOptions options) { PipelineMessage message = Shim.Pipeline.CreateMessage(); message.ResponseClassifier = ResponseErrorClassifier200; + PipelineRequest request = message.Request; request.Method = "POST"; + UriBuilder uriBuilder = new(_clientConnector.Endpoint.AbsoluteUri); + StringBuilder path = new(); path.Append("/images/edits"); uriBuilder.Path += path.ToString(); - request.Uri = uriBuilder.Uri; - - options ??= new(); - MultipartFormDataContent requestContent = CreateInternalImageEditsMultipartFormDataContent( - imageBytes, - prompt, - options.MaskBytes, - imageCount, - options.ResponseFormat, - options.Size, - options.User); - requestContent.ApplyToRequest(request); - - return message; - } - - private MultipartFormDataContent CreateInternalImageEditsMultipartFormDataContent( - BinaryData imageBytes, - string prompt, - BinaryData maskBytes, - int? imageCount, - ImageResponseFormat? imageResponseFormat, - ImageSize? imageSize, - string user) - { - MultipartFormDataContent content = new(); - content.Add(MultipartContent.Create(imageBytes), name: "image", fileName: "image.png", headers: []); - - content.Add(MultipartContent.Create(BinaryData.FromString(prompt)), name: "prompt", headers: []); - - content.Add(MultipartContent.Create(BinaryData.FromString(_clientConnector.Model)), name: "model", headers: []); - - if (Optional.IsDefined(maskBytes)) - { - content.Add(MultipartContent.Create(maskBytes), name: "mask", fileName: "mask.png", headers: []); - } + request.Uri = uriBuilder.Uri; - if (Optional.IsDefined(imageCount)) - { - content.Add(MultipartContent.Create(BinaryData.FromString(imageCount.ToString())), name: "n", headers: []); - } + request.Headers.Set("Content-Type", contentType); - if (Optional.IsDefined(imageResponseFormat)) - { - content.Add(MultipartContent.Create( - BinaryData.FromString( - imageResponseFormat switch - { - ImageResponseFormat.Uri => "url", - ImageResponseFormat.Bytes => "b64_json", - _ => throw new ArgumentException(nameof(imageResponseFormat)), - }) - ), - name: "response_format", - headers: []); - } + request.Content = content; - if (Optional.IsDefined(imageSize)) - { - content.Add(MultipartContent.Create( - BinaryData.FromString( - imageSize switch - { - ImageSize.Size256x256 => "256x256", - ImageSize.Size512x512 => "512x512", - ImageSize.Size1024x1024 => "1024x1024", - // TODO: 1024x1792 and 1792x1024 are currently not supported in image edits. - ImageSize.Size1024x1792 => "1024x1792", - ImageSize.Size1792x1024 => "1792x1024", - _ => throw new ArgumentException(nameof(imageSize)) - }) - ), - name: "size", - headers: []); - } + message.Apply(options); - if (Optional.IsDefined(user)) - { - content.Add(MultipartContent.Create(BinaryData.FromString(user)), "user", []); - } - - return content; + return message; } - private PipelineMessage CreateInternalImageVariationsPipelineMessage( - BinaryData imageBytes, - int? imageCount = null, - ImageVariationOptions options = null) + private PipelineMessage CreateImageVariationsRequest(BinaryContent content, string contentType, RequestOptions options) { PipelineMessage message = Shim.Pipeline.CreateMessage(); message.ResponseClassifier = ResponseErrorClassifier200; + PipelineRequest request = message.Request; request.Method = "POST"; + UriBuilder uriBuilder = new(_clientConnector.Endpoint.AbsoluteUri); + StringBuilder path = new(); path.Append("/images/variations"); uriBuilder.Path += path.ToString(); - request.Uri = uriBuilder.Uri; - - options ??= new(); - MultipartFormDataContent requestContent = CreateInternalImageVariationsMultipartFormDataContent( - imageBytes, - imageCount, - options.ResponseFormat, - options.Size, - options.User); - requestContent.ApplyToRequest(request); - - return message; - } - - private MultipartFormDataContent CreateInternalImageVariationsMultipartFormDataContent( - BinaryData imageBytes, - int? imageCount, - ImageResponseFormat? imageResponseFormat, - ImageSize? imageSize, - string user) - { - MultipartFormDataContent content = new(); - - content.Add(MultipartContent.Create(imageBytes), name: "image", fileName: "image.png", headers: []); - - content.Add(MultipartContent.Create(BinaryData.FromString(_clientConnector.Model)), name: "model", headers: []); - if (Optional.IsDefined(imageCount)) - { - content.Add(MultipartContent.Create(BinaryData.FromString(imageCount.ToString())), name: "n", headers: []); - } + request.Uri = uriBuilder.Uri; - if (Optional.IsDefined(imageResponseFormat)) - { - content.Add(MultipartContent.Create( - BinaryData.FromString( - imageResponseFormat switch - { - ImageResponseFormat.Uri => "url", - ImageResponseFormat.Bytes => "b64_json", - _ => throw new ArgumentException(nameof(imageResponseFormat)), - }) - ), - name: "response_format", - headers: []); - } + request.Headers.Set("Content-Type", contentType); - if (Optional.IsDefined(imageSize)) - { - content.Add(MultipartContent.Create( - BinaryData.FromString( - imageSize switch - { - ImageSize.Size256x256 => "256x256", - ImageSize.Size512x512 => "512x512", - ImageSize.Size1024x1024 => "1024x1024", - // TODO: 1024x1792 and 1792x1024 are currently not supported in image variations. - ImageSize.Size1024x1792 => "1024x1792", - ImageSize.Size1792x1024 => "1792x1024", - _ => throw new ArgumentException(nameof(imageSize)) - }) - ), - name: "size", - headers: []); - } + request.Content = content; - if (Optional.IsDefined(user)) - { - content.Add(MultipartContent.Create(BinaryData.FromString(user)), "user", []); - } + message.Apply(options); - return content; + return message; } private static PipelineMessageClassifier _responseErrorClassifier200; private static PipelineMessageClassifier ResponseErrorClassifier200 => _responseErrorClassifier200 ??= PipelineMessageClassifier.Create(stackalloc ushort[] { 200 }); - - private Internal.Models.CreateImageEditRequest CreateInternalImageEditRequest( - BinaryData imageBytes, - string prompt, - int? imageCount = null, - ImageEditOptions options = null) - { - options ??= new(); - - - Internal.Models.CreateImageEditRequestSize? internalSize = null; - if (options.Size != null) - { - internalSize = options.Size switch - { - - ImageSize.Size256x256 => Internal.Models.CreateImageEditRequestSize._256x256, - ImageSize.Size512x512 => Internal.Models.CreateImageEditRequestSize._512x512, - ImageSize.Size1024x1024 => Internal.Models.CreateImageEditRequestSize._1024x1024, - // TODO: 1024x1792 and 1792x1024 are currently not supported in image edits. - ImageSize.Size1024x1792 => new Internal.Models.CreateImageEditRequestSize("1024x1792"), - ImageSize.Size1792x1024 => new Internal.Models.CreateImageEditRequestSize("1792x1024"), - _ => throw new ArgumentException(nameof(options.Size)), - }; - } - - Internal.Models.CreateImageEditRequestResponseFormat? internalFormat = null; - if (options.ResponseFormat != null) - { - internalFormat = options.ResponseFormat switch - { - ImageResponseFormat.Bytes => Internal.Models.CreateImageEditRequestResponseFormat.B64Json, - ImageResponseFormat.Uri => Internal.Models.CreateImageEditRequestResponseFormat.Url, - _ => throw new ArgumentException(nameof(options.ResponseFormat)), - }; - } - - return new Internal.Models.CreateImageEditRequest( - imageBytes, - prompt, - options.MaskBytes, - _clientConnector.Model, - imageCount, - internalSize, - internalFormat, - options.User, - serializedAdditionalRawData: null); - } } diff --git a/.dotnet/src/Custom/Images/ImageEditOptions.cs b/.dotnet/src/Custom/Images/ImageEditOptions.cs index 8412a42ce..009d3929a 100644 --- a/.dotnet/src/Custom/Images/ImageEditOptions.cs +++ b/.dotnet/src/Custom/Images/ImageEditOptions.cs @@ -1,4 +1,7 @@ -using System; +using OpenAI.Internal; +using System; +using System.ClientModel.Primitives; +using System.IO; namespace OpenAI.Images; @@ -8,14 +11,69 @@ namespace OpenAI.Images; public partial class ImageEditOptions { /// - public BinaryData MaskBytes { get; set; } + public Stream Mask { get; set; } - /// - public ImageSize? Size { get; set; } + // The generator will need to add file-name to models for properties that + // represent files in order to enable setting the header. + /// + /// TODO + /// + public string MaskFileName { get; set; } /// public ImageResponseFormat? ResponseFormat { get; set; } + /// + public GeneratedImageSize Size { get; set; } + /// public string User { get; set; } + + internal MultipartFormDataBinaryContent ToMultipartContent(Stream image, + string fileName, + string prompt, + string model, + int? imageCount) + { + MultipartFormDataBinaryContent content = new(); + + content.Add(image, "image", fileName); + content.Add(prompt, "prompt"); + content.Add(model, "model"); + + if (Mask is not null) + { + content.Add(Mask, "mask", MaskFileName); + } + + if (imageCount is not null) + { + content.Add(imageCount.Value, "n"); + } + + if (ResponseFormat is not null) + { + string format = ResponseFormat switch + { + ImageResponseFormat.Uri => "url", + ImageResponseFormat.Bytes => "b64_json", + _ => throw new ArgumentException(nameof(ResponseFormat)), + }; + + content.Add(format, "response_format"); + } + + if (Size is not null) + { + string imageSize = ModelReaderWriter.Write(Size).ToString(); + content.Add(imageSize, "size"); + } + + if (User is not null) + { + content.Add(User, "user"); + } + + return content; + } } \ No newline at end of file diff --git a/.dotnet/src/Custom/Images/ImageGenerationOptions.cs b/.dotnet/src/Custom/Images/ImageGenerationOptions.cs index 37cdb95af..289653f4b 100644 --- a/.dotnet/src/Custom/Images/ImageGenerationOptions.cs +++ b/.dotnet/src/Custom/Images/ImageGenerationOptions.cs @@ -39,21 +39,21 @@ public partial class ImageGenerationOptions /// /// Available for dall-e-2: /// - /// 1024x1024 - - default - /// 256x256 - - small - /// 512x512 - - medium + /// 1024x1024 - - default + /// 256x256 - - small + /// 512x512 - - medium /// /// /// /// Available for dall-e-3: /// - /// 1024x1024 - - default - /// 1024x1792 - - extra tall - /// 1792x1024 - - extra wide + /// 1024x1024 - - default + /// 1024x1792 - - extra tall + /// 1792x1024 - - extra wide /// /// /// - public ImageSize? Size { get; set; } + public GeneratedImageSize Size { get; set; } /// /// The style kind to guide the generation of the image. /// diff --git a/.dotnet/src/Custom/Images/ImageSize.Serialization.cs b/.dotnet/src/Custom/Images/ImageSize.Serialization.cs new file mode 100644 index 000000000..77da27dbc --- /dev/null +++ b/.dotnet/src/Custom/Images/ImageSize.Serialization.cs @@ -0,0 +1,80 @@ +using OpenAI.Internal.Models; +using System; +using System.ClientModel.Primitives; +using System.Linq; +using System.Text.Json; + +namespace OpenAI.Images; + +/// +/// Represents the available output dimensions for generated images. +/// +public partial class GeneratedImageSize : IJsonModel +{ + GeneratedImageSize IJsonModel.Create(ref Utf8JsonReader reader, ModelReaderWriterOptions options) + { + var format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; + if (format != "J") + { + throw new FormatException($"The model {nameof(GeneratedImageSize)} does not support '{format}' format."); + } + + using JsonDocument document = JsonDocument.ParseValue(ref reader); + return DeserializeGeneratedImageSize(document.RootElement, options); + } + + GeneratedImageSize IPersistableModel.Create(BinaryData data, ModelReaderWriterOptions options) + { + var format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; + + switch (format) + { + case "J": + { + using JsonDocument document = JsonDocument.Parse(data); + return DeserializeGeneratedImageSize(document.RootElement, options); + } + default: + throw new FormatException($"The model {nameof(GeneratedImageSize)} does not support '{options.Format}' format."); + } + } + + string IPersistableModel.GetFormatFromOptions(ModelReaderWriterOptions options) => "J"; + + void IJsonModel.Write(Utf8JsonWriter writer, ModelReaderWriterOptions options) + { + var format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; + if (format != "J") + { + throw new FormatException($"The model {nameof(ChatCompletionFunctionCallOption)} does not support '{format}' format."); + } + writer.WriteStringValue($"{Width}x{Height}"); + } + + BinaryData IPersistableModel.Write(ModelReaderWriterOptions options) + { + var format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; + + switch (format) + { + case "J": + return ModelReaderWriter.Write(this, options); + default: + throw new FormatException($"The model {nameof(GeneratedImageSize)} does not support '{options.Format}' format."); + } + } + + internal static GeneratedImageSize DeserializeGeneratedImageSize(JsonElement element, ModelReaderWriterOptions options = null) + { + if (element.ValueKind != JsonValueKind.String) + { + return null; + } + string[] parts = element.GetString().Split('x'); + if (parts.Length != 2 || !int.TryParse(parts[0], out int width) || !int.TryParse(parts[1], out int height)) + { + return null; + } + return new GeneratedImageSize(width, height); + } +} \ No newline at end of file diff --git a/.dotnet/src/Custom/Images/ImageSize.cs b/.dotnet/src/Custom/Images/ImageSize.cs deleted file mode 100644 index c857509d1..000000000 --- a/.dotnet/src/Custom/Images/ImageSize.cs +++ /dev/null @@ -1,43 +0,0 @@ -namespace OpenAI.Images; - -/// -/// Represents the available output dimensions for generated images. -/// -public enum ImageSize -{ - /// - /// A small, square image with 256 pixels of both width and height. - /// - /// Supported only for the older dall-e-2 model. - /// - /// - Size256x256, - /// - /// A medium-small, square image with 512 pixels of both width and height. - /// - /// Supported only for the older dall-e-2 model. - /// - /// - Size512x512, - /// - /// A square image with 1024 pixels of both width and height. - /// - /// Supported and default for both dall-e-2 and dall-e-3 models. - /// - /// - Size1024x1024, - /// - /// An extra tall image, 1024 pixels wide by 1792 pixels high. - /// - /// Supported only for the dall-e-3 model. - /// - /// - Size1024x1792, - /// - /// An extra wide image, 1792 pixels wide by 1024 pixels high. - /// - /// Supported only for the dall-e-3 model. - /// - /// - Size1792x1024, -} \ No newline at end of file diff --git a/.dotnet/src/Custom/Images/ImageVariationOptions.cs b/.dotnet/src/Custom/Images/ImageVariationOptions.cs index 129c14cb7..911251d56 100644 --- a/.dotnet/src/Custom/Images/ImageVariationOptions.cs +++ b/.dotnet/src/Custom/Images/ImageVariationOptions.cs @@ -1,4 +1,7 @@ -using System; +using OpenAI.Internal; +using System; +using System.ClientModel.Primitives; +using System.IO; namespace OpenAI.Images; @@ -8,11 +11,49 @@ namespace OpenAI.Images; public partial class ImageVariationOptions { /// - public ImageSize? Size { get; set; } + public GeneratedImageSize Size { get; set; } /// public ImageResponseFormat? ResponseFormat { get; set; } /// public string User { get; set; } + + internal MultipartFormDataBinaryContent ToMultipartContent(Stream image, string fileName, string model, int? imageCount) + { + MultipartFormDataBinaryContent content = new(); + + content.Add(image, "image", fileName); + content.Add(model, "model"); + + if (imageCount is not null) + { + content.Add(imageCount.Value, "n"); + } + + if (ResponseFormat is not null) + { + string format = ResponseFormat switch + { + ImageResponseFormat.Uri => "url", + ImageResponseFormat.Bytes => "b64_json", + _ => throw new ArgumentException(nameof(ResponseFormat)), + }; + + content.Add(format, "response_format"); + } + + if (Size is not null) + { + string imageSize = ModelReaderWriter.Write(Size).ToString(); + content.Add(imageSize, "size"); + } + + if (User is not null) + { + content.Add(User, "user"); + } + + return content; + } } \ No newline at end of file diff --git a/.dotnet/src/Custom/LegacyCompletions/LegacyCompletionClient.cs b/.dotnet/src/Custom/LegacyCompletions/LegacyCompletionClient.cs index 460691149..fd2b2b75a 100644 --- a/.dotnet/src/Custom/LegacyCompletions/LegacyCompletionClient.cs +++ b/.dotnet/src/Custom/LegacyCompletions/LegacyCompletionClient.cs @@ -28,67 +28,10 @@ public partial class LegacyCompletionClient /// if it is defined. /// /// - /// The connection endpoint to use. /// The API key used to authenticate with the service endpoint. /// Additional options to customize the client. - public LegacyCompletionClient(Uri endpoint, ApiKeyCredential credential, OpenAIClientOptions options = null) + public LegacyCompletionClient(ApiKeyCredential credential = default, OpenAIClientOptions options = default) { - _clientConnector = new("protocol", endpoint, credential, options); + _clientConnector = new(model: null, credential, options); } - - /// - /// Initializes a new instance of , used for legacy completion operation requests. - /// - /// - /// - /// If an endpoint is not provided, the client will use the OPENAI_ENDPOINT environment variable if it - /// defined and otherwise use the default OpenAI v1 endpoint. - /// - /// - /// If an authentication credential is not defined, the client use the OPENAI_API_KEY environment variable - /// if it is defined. - /// - /// - /// The connection endpoint to use. - /// Additional options to customize the client. - public LegacyCompletionClient(Uri endpoint, OpenAIClientOptions options = null) - : this(endpoint, credential: null, options) - { } - - /// - /// Initializes a new instance of , used for legacy completion operation requests. - /// - /// - /// - /// If an endpoint is not provided, the client will use the OPENAI_ENDPOINT environment variable if it - /// defined and otherwise use the default OpenAI v1 endpoint. - /// - /// - /// If an authentication credential is not defined, the client use the OPENAI_API_KEY environment variable - /// if it is defined. - /// - /// - /// The API key used to authenticate with the service endpoint. - /// Additional options to customize the client. - public LegacyCompletionClient(ApiKeyCredential credential, OpenAIClientOptions options = null) - : this(endpoint: null, credential, options) - { } - - /// - /// Initializes a new instance of , used for legacy completion operation requests. - /// - /// - /// - /// If an endpoint is not provided, the client will use the OPENAI_ENDPOINT environment variable if it - /// defined and otherwise use the default OpenAI v1 endpoint. - /// - /// - /// If an authentication credential is not defined, the client use the OPENAI_API_KEY environment variable - /// if it is defined. - /// - /// - /// Additional options to customize the client. - public LegacyCompletionClient(OpenAIClientOptions options = null) - : this(endpoint: null, credential: null, options) - { } } diff --git a/.dotnet/src/Custom/Models/ModelManagementClient.cs b/.dotnet/src/Custom/Models/ModelManagementClient.cs index 06ea5e028..fad3d3615 100644 --- a/.dotnet/src/Custom/Models/ModelManagementClient.cs +++ b/.dotnet/src/Custom/Models/ModelManagementClient.cs @@ -26,70 +26,13 @@ public partial class ModelManagementClient /// if it is defined. /// /// - /// The connection endpoint to use. /// The API key used to authenticate with the service endpoint. /// Additional options to customize the client. - public ModelManagementClient(Uri endpoint, ApiKeyCredential credential, OpenAIClientOptions options = null) + public ModelManagementClient(ApiKeyCredential credential = default, OpenAIClientOptions options = default) { - _clientConnector = new("none", endpoint, credential, options); + _clientConnector = new(model: "none", credential, options); } - /// - /// Initializes a new instance of , used for model operation requests. - /// - /// - /// - /// If an endpoint is not provided, the client will use the OPENAI_ENDPOINT environment variable if it - /// defined and otherwise use the default OpenAI v1 endpoint. - /// - /// - /// If an authentication credential is not defined, the client use the OPENAI_API_KEY environment variable - /// if it is defined. - /// - /// - /// The connection endpoint to use. - /// Additional options to customize the client. - public ModelManagementClient(Uri endpoint, OpenAIClientOptions options = null) - : this(endpoint, credential: null, options) - { } - - /// - /// Initializes a new instance of , used for model operation requests. - /// - /// - /// - /// If an endpoint is not provided, the client will use the OPENAI_ENDPOINT environment variable if it - /// defined and otherwise use the default OpenAI v1 endpoint. - /// - /// - /// If an authentication credential is not defined, the client use the OPENAI_API_KEY environment variable - /// if it is defined. - /// - /// - /// The API key used to authenticate with the service endpoint. - /// Additional options to customize the client. - public ModelManagementClient(ApiKeyCredential credential, OpenAIClientOptions options = null) - : this(endpoint: null, credential, options) - { } - - /// - /// Initializes a new instance of , used for model operation requests. - /// - /// - /// - /// If an endpoint is not provided, the client will use the OPENAI_ENDPOINT environment variable if it - /// defined and otherwise use the default OpenAI v1 endpoint. - /// - /// - /// If an authentication credential is not defined, the client use the OPENAI_API_KEY environment variable - /// if it is defined. - /// - /// - /// Additional options to customize the client. - public ModelManagementClient(OpenAIClientOptions options = null) - : this(endpoint: null, credential: null, options) - { } - public virtual ClientResult GetModelInfo(string modelId) { ClientResult internalResult = Shim.Retrieve(modelId); diff --git a/.dotnet/src/Custom/Moderations/ModerationClient.cs b/.dotnet/src/Custom/Moderations/ModerationClient.cs index 640baf66c..bfda1e411 100644 --- a/.dotnet/src/Custom/Moderations/ModerationClient.cs +++ b/.dotnet/src/Custom/Moderations/ModerationClient.cs @@ -1,4 +1,3 @@ -using System; using System.ClientModel; namespace OpenAI.Moderations; @@ -24,67 +23,10 @@ public partial class ModerationClient /// if it is defined. /// /// - /// The connection endpoint to use. /// The API key used to authenticate with the service endpoint. /// Additional options to customize the client. - public ModerationClient(Uri endpoint, ApiKeyCredential credential, OpenAIClientOptions options = null) + public ModerationClient(ApiKeyCredential credential = default, OpenAIClientOptions options = default) { - _clientConnector = new("none", endpoint, credential, options); + _clientConnector = new(model: null, credential, options); } - - /// - /// Initializes a new instance of , used for moderation operation requests. - /// - /// - /// - /// If an endpoint is not provided, the client will use the OPENAI_ENDPOINT environment variable if it - /// defined and otherwise use the default OpenAI v1 endpoint. - /// - /// - /// If an authentication credential is not defined, the client use the OPENAI_API_KEY environment variable - /// if it is defined. - /// - /// - /// The connection endpoint to use. - /// Additional options to customize the client. - public ModerationClient(Uri endpoint, OpenAIClientOptions options = null) - : this(endpoint, credential: null, options) - { } - - /// - /// Initializes a new instance of , used for moderation operation requests. - /// - /// - /// - /// If an endpoint is not provided, the client will use the OPENAI_ENDPOINT environment variable if it - /// defined and otherwise use the default OpenAI v1 endpoint. - /// - /// - /// If an authentication credential is not defined, the client use the OPENAI_API_KEY environment variable - /// if it is defined. - /// - /// - /// The API key used to authenticate with the service endpoint. - /// Additional options to customize the client. - public ModerationClient(ApiKeyCredential credential, OpenAIClientOptions options = null) - : this(endpoint: null, credential, options) - { } - - /// - /// Initializes a new instance of , used for moderation operation requests. - /// - /// - /// - /// If an endpoint is not provided, the client will use the OPENAI_ENDPOINT environment variable if it - /// defined and otherwise use the default OpenAI v1 endpoint. - /// - /// - /// If an authentication credential is not defined, the client use the OPENAI_API_KEY environment variable - /// if it is defined. - /// - /// - /// Additional options to customize the client. - public ModerationClient(OpenAIClientOptions options = null) - : this(endpoint: null, credential: null, options) - { } } diff --git a/.dotnet/src/Custom/OpenAIClient.cs b/.dotnet/src/Custom/OpenAIClient.cs index 3971d594e..0675b193c 100644 --- a/.dotnet/src/Custom/OpenAIClient.cs +++ b/.dotnet/src/Custom/OpenAIClient.cs @@ -5,11 +5,13 @@ using OpenAI.Files; using OpenAI.FineTuningManagement; using OpenAI.Images; +using OpenAI.Internal.Models; using OpenAI.LegacyCompletions; using OpenAI.ModelManagement; using OpenAI.Moderations; using System; using System.ClientModel; +using System.Diagnostics.CodeAnalysis; namespace OpenAI; @@ -19,7 +21,6 @@ namespace OpenAI; /// public partial class OpenAIClient { - private readonly Uri _cachedEndpoint = null; private readonly ApiKeyCredential _cachedCredential = null; private readonly OpenAIClientOptions _cachedOptions = null; @@ -31,57 +32,14 @@ public partial class OpenAIClient /// This client does not provide any model functionality directly and is purely a helper to facilitate the creation /// of the scenario-specific subclients like . /// - /// An explicitly defined endpoint that all clients created by this should use. /// An explicitly defined credential that all clients created by this should use. - /// A common client options definition that all clients created by this should use. - public OpenAIClient(Uri endpoint, ApiKeyCredential credential, OpenAIClientOptions clientOptions = null) + /// A common client options definition that all clients created by this should use. + public OpenAIClient(ApiKeyCredential credential = default, OpenAIClientOptions options = default) { - _cachedEndpoint = endpoint; _cachedCredential = credential; - _cachedOptions = clientOptions; + _cachedOptions = options; } - /// - /// Creates a new instance of will store common client configuration details to permit - /// easy reuse and propagation to multiple, scenario-specific subclients. - /// - /// - /// This client does not provide any model functionality directly and is purely a helper to facilitate the creation - /// of the scenario-specific subclients like . - /// - /// An explicitly defined endpoint that all clients created by this should use. - /// A common client options definition that all clients created by this should use. - public OpenAIClient(Uri endpoint, OpenAIClientOptions clientOptions = null) - : this(endpoint, credential: null, clientOptions) - { } - - /// - /// Creates a new instance of will store common client configuration details to permit - /// easy reuse and propagation to multiple, scenario-specific subclients. - /// - /// - /// This client does not provide any model functionality directly and is purely a helper to facilitate the creation - /// of the scenario-specific subclients like . - /// - /// An explicitly defined credential that all clients created by this should use. - /// A common client options definition that all clients created by this should use. - public OpenAIClient(ApiKeyCredential credential, OpenAIClientOptions clientOptions = null) - : this(endpoint: null, credential, clientOptions) - { } - - /// - /// Creates a new instance of will store common client configuration details to permit - /// easy reuse and propagation to multiple, scenario-specific subclients. - /// - /// - /// This client does not provide any model functionality directly and is purely a helper to facilitate the creation - /// of the scenario-specific subclients like . - /// - /// A common client options definition that all clients created by this should use. - public OpenAIClient(OpenAIClientOptions clientOptions) - : this(endpoint: null, credential: null, clientOptions) - { } - /// /// Gets a new instance of that reuses the client configuration details provided to /// the instance. @@ -91,8 +49,8 @@ public OpenAIClient(OpenAIClientOptions clientOptions) /// the same configuration details. /// /// A new . - public AssistantClient GetAssistantClient() - => new(_cachedEndpoint, _cachedCredential, _cachedOptions); + [Experimental("OPENAI001")] + public AssistantClient GetAssistantClient() => new(_cachedCredential, _cachedOptions); /// /// Gets a new instance of that reuses the client configuration details provided to @@ -103,8 +61,7 @@ public AssistantClient GetAssistantClient() /// the same configuration details. /// /// A new . - public AudioClient GetAudioClient(string model) - => new(_cachedEndpoint, model, _cachedCredential, _cachedOptions); + public AudioClient GetAudioClient(string model) => new(model, _cachedCredential, _cachedOptions); /// /// Gets a new instance of that reuses the client configuration details provided to @@ -115,8 +72,7 @@ public AudioClient GetAudioClient(string model) /// the same configuration details. /// /// A new . - public ChatClient GetChatClient(string model) - => new(_cachedEndpoint, model, _cachedCredential, _cachedOptions); + public ChatClient GetChatClient(string model) => new(model, _cachedCredential, _cachedOptions); /// /// Gets a new instance of that reuses the client configuration details provided to @@ -127,8 +83,7 @@ public ChatClient GetChatClient(string model) /// the same configuration details. /// /// A new . - public EmbeddingClient GetEmbeddingClient(string model) - => new(_cachedEndpoint, model, _cachedCredential, _cachedOptions); + public EmbeddingClient GetEmbeddingClient(string model) => new(model, _cachedCredential, _cachedOptions); /// /// Gets a new instance of that reuses the client configuration details provided to @@ -139,8 +94,7 @@ public EmbeddingClient GetEmbeddingClient(string model) /// the same configuration details. /// /// A new . - public FileClient GetFileClient() - => new(_cachedEndpoint, _cachedCredential, _cachedOptions); + public FileClient GetFileClient() => new(_cachedCredential, _cachedOptions); /// /// Gets a new instance of that reuses the client configuration details provided to @@ -151,8 +105,7 @@ public FileClient GetFileClient() /// the same configuration details. /// /// A new . - public FineTuningManagementClient GetFineTuningManagementClient() - => new(_cachedEndpoint, _cachedCredential, _cachedOptions); + public FineTuningManagementClient GetFineTuningManagementClient() => new(_cachedCredential, _cachedOptions); /// /// Gets a new instance of that reuses the client configuration details provided to @@ -163,8 +116,7 @@ public FineTuningManagementClient GetFineTuningManagementClient() /// the same configuration details. /// /// A new . - public ImageClient GetImageClient(string model) - => new(_cachedEndpoint, model, _cachedCredential, _cachedOptions); + public ImageClient GetImageClient(string model) => new(model, _cachedCredential, _cachedOptions); /// /// Gets a new instance of that reuses the client configuration details provided to @@ -175,8 +127,7 @@ public ImageClient GetImageClient(string model) /// the same configuration details. /// /// A new . - public LegacyCompletionClient GetLegacyCompletionClient() - => new(_cachedEndpoint, _cachedCredential, _cachedOptions); + public LegacyCompletionClient GetLegacyCompletionClient() => new(_cachedCredential, _cachedOptions); /// /// Gets a new instance of that reuses the client configuration details provided to @@ -187,8 +138,7 @@ public LegacyCompletionClient GetLegacyCompletionClient() /// the same configuration details. /// /// A new . - public ModelManagementClient GetModelManagementClient() - => new(_cachedEndpoint, _cachedCredential, _cachedOptions); + public ModelManagementClient GetModelManagementClient() => new(_cachedCredential, _cachedOptions); /// /// Gets a new instance of that reuses the client configuration details provided to @@ -199,6 +149,5 @@ public ModelManagementClient GetModelManagementClient() /// the same configuration details. /// /// A new . - public ModerationClient GetModerationClient() - => new(_cachedEndpoint, _cachedCredential, _cachedOptions); + public ModerationClient GetModerationClient() => new(_cachedCredential, _cachedOptions); } diff --git a/.dotnet/src/Custom/OpenAIClientConnector.cs b/.dotnet/src/Custom/OpenAIClientConnector.cs index 348d5f48b..8ee47b1ee 100644 --- a/.dotnet/src/Custom/OpenAIClientConnector.cs +++ b/.dotnet/src/Custom/OpenAIClientConnector.cs @@ -19,13 +19,12 @@ internal partial class OpenAIClientConnector internal OpenAIClientConnector( string model, - Uri endpoint = null, ApiKeyCredential credential = null, OpenAIClientOptions options = null) { if (model is null) throw new ArgumentNullException(nameof(model)); Model = model; - Endpoint ??= new(Environment.GetEnvironmentVariable(s_OpenAIEndpointEnvironmentVariable) ?? s_defaultOpenAIV1Endpoint); + Endpoint ??= options?.Endpoint ?? new(Environment.GetEnvironmentVariable(s_OpenAIEndpointEnvironmentVariable) ?? s_defaultOpenAIV1Endpoint); credential ??= new(Environment.GetEnvironmentVariable(s_OpenAIApiKeyEnvironmentVariable) ?? string.Empty); options ??= new(); InternalClient = new(Endpoint, credential, options.InternalOptions); diff --git a/.dotnet/src/Custom/OpenAIClientOptions.cs b/.dotnet/src/Custom/OpenAIClientOptions.cs index 69eb0b2d6..8234691b5 100644 --- a/.dotnet/src/Custom/OpenAIClientOptions.cs +++ b/.dotnet/src/Custom/OpenAIClientOptions.cs @@ -1,3 +1,4 @@ +using System; using System.ClientModel; using System.ClientModel; using System.ClientModel.Primitives; @@ -10,6 +11,11 @@ namespace OpenAI; /// public partial class OpenAIClientOptions : RequestOptions { + /// + /// Gets or sets a non-default base endpoint that clients should use when connecting. + /// + public Uri Endpoint { get; set; } + // Note: this type currently proxies RequestOptions properties manually via the matching internal type. This is a // temporary extra step pending richer integration with code generation. diff --git a/.dotnet/src/Directory.Build.props b/.dotnet/src/Directory.Build.props index 14f9c42e7..21f972465 100644 --- a/.dotnet/src/Directory.Build.props +++ b/.dotnet/src/Directory.Build.props @@ -1,6 +1,6 @@ - 1.0.0 + 0.1.0 beta.1 true OpenAI.snk diff --git a/.dotnet/src/Generated/Audio.cs b/.dotnet/src/Generated/Audio.cs index 2b73c9657..3b55f1f12 100644 --- a/.dotnet/src/Generated/Audio.cs +++ b/.dotnet/src/Generated/Audio.cs @@ -144,25 +144,25 @@ public virtual ClientResult CreateSpeech(BinaryContent content, RequestOptions o /// Transcribes audio into the input language. /// The to use. /// is null. - public virtual async Task> CreateTranscriptionAsync(CreateTranscriptionRequest audio) + public virtual async Task> CreateTranscriptionAsync(CreateTranscriptionRequest audio) { Argument.AssertNotNull(audio, nameof(audio)); using BinaryContent content = BinaryContent.Create(audio); ClientResult result = await CreateTranscriptionAsync(content, DefaultRequestContext).ConfigureAwait(false); - return ClientResult.FromValue(CreateTranscriptionResponse.FromResponse(result.GetRawResponse()), result.GetRawResponse()); + return ClientResult.FromValue(CreateTranscriptionResponseVerboseJson.FromResponse(result.GetRawResponse()), result.GetRawResponse()); } /// Transcribes audio into the input language. /// The to use. /// is null. - public virtual ClientResult CreateTranscription(CreateTranscriptionRequest audio) + public virtual ClientResult CreateTranscription(CreateTranscriptionRequest audio) { Argument.AssertNotNull(audio, nameof(audio)); using BinaryContent content = BinaryContent.Create(audio); ClientResult result = CreateTranscription(content, DefaultRequestContext); - return ClientResult.FromValue(CreateTranscriptionResponse.FromResponse(result.GetRawResponse()), result.GetRawResponse()); + return ClientResult.FromValue(CreateTranscriptionResponseVerboseJson.FromResponse(result.GetRawResponse()), result.GetRawResponse()); } /// @@ -246,25 +246,25 @@ public virtual ClientResult CreateTranscription(BinaryContent content, RequestOp /// Translates audio into English.. /// The to use. /// is null. - public virtual async Task> CreateTranslationAsync(CreateTranslationRequest audio) + public virtual async Task> CreateTranslationAsync(CreateTranslationRequest audio) { Argument.AssertNotNull(audio, nameof(audio)); using BinaryContent content = BinaryContent.Create(audio); ClientResult result = await CreateTranslationAsync(content, DefaultRequestContext).ConfigureAwait(false); - return ClientResult.FromValue(CreateTranslationResponse.FromResponse(result.GetRawResponse()), result.GetRawResponse()); + return ClientResult.FromValue(CreateTranslationResponseVerboseJson.FromResponse(result.GetRawResponse()), result.GetRawResponse()); } /// Translates audio into English.. /// The to use. /// is null. - public virtual ClientResult CreateTranslation(CreateTranslationRequest audio) + public virtual ClientResult CreateTranslation(CreateTranslationRequest audio) { Argument.AssertNotNull(audio, nameof(audio)); using BinaryContent content = BinaryContent.Create(audio); ClientResult result = CreateTranslation(content, DefaultRequestContext); - return ClientResult.FromValue(CreateTranslationResponse.FromResponse(result.GetRawResponse()), result.GetRawResponse()); + return ClientResult.FromValue(CreateTranslationResponseVerboseJson.FromResponse(result.GetRawResponse()), result.GetRawResponse()); } /// diff --git a/.dotnet/src/Generated/Models/AudioSegment.cs b/.dotnet/src/Generated/Models/AudioSegment.cs deleted file mode 100644 index ac48fd377..000000000 --- a/.dotnet/src/Generated/Models/AudioSegment.cs +++ /dev/null @@ -1,145 +0,0 @@ -// - -using System; -using System.Collections.Generic; -using System.Linq; -using OpenAI; - -namespace OpenAI.Internal.Models -{ - /// The AudioSegment. - internal partial class AudioSegment - { - /// - /// Keeps track of any properties unknown to the library. - /// - /// To assign an object to the value of this property use . - /// - /// - /// To assign an already formatted json string to this property use . - /// - /// - /// Examples: - /// - /// - /// BinaryData.FromObjectAsJson("foo") - /// Creates a payload of "foo". - /// - /// - /// BinaryData.FromString("\"foo\"") - /// Creates a payload of "foo". - /// - /// - /// BinaryData.FromObjectAsJson(new { key = "value" }) - /// Creates a payload of { "key": "value" }. - /// - /// - /// BinaryData.FromString("{\"key\": \"value\"}") - /// Creates a payload of { "key": "value" }. - /// - /// - /// - /// - private IDictionary _serializedAdditionalRawData; - - /// Initializes a new instance of . - /// The zero-based index of this segment. - /// - /// The seek position associated with the processing of this audio segment. Seek positions are - /// expressed as hundredths of seconds. The model may process several segments from a single seek - /// position, so while the seek position will never represent a later time than the segment's - /// start, the segment's start may represent a significantly later time than the segment's - /// associated seek position. - /// - /// The time at which this segment started relative to the beginning of the audio. - /// The time at which this segment ended relative to the beginning of the audio. - /// The text that was part of this audio segment. - /// The token IDs matching the text in this audio segment. - /// The temperature score associated with this audio segment. - /// The average log probability associated with this audio segment. - /// The compression ratio of this audio segment. - /// The probability of no speech detection within this audio segment. - /// or is null. - internal AudioSegment(long id, long seek, TimeSpan start, TimeSpan end, string text, IEnumerable tokens, double temperature, double avgLogprob, double compressionRatio, double noSpeechProb) - { - Argument.AssertNotNull(text, nameof(text)); - Argument.AssertNotNull(tokens, nameof(tokens)); - - Id = id; - Seek = seek; - Start = start; - End = end; - Text = text; - Tokens = tokens.ToList(); - Temperature = temperature; - AvgLogprob = avgLogprob; - CompressionRatio = compressionRatio; - NoSpeechProb = noSpeechProb; - } - - /// Initializes a new instance of . - /// The zero-based index of this segment. - /// - /// The seek position associated with the processing of this audio segment. Seek positions are - /// expressed as hundredths of seconds. The model may process several segments from a single seek - /// position, so while the seek position will never represent a later time than the segment's - /// start, the segment's start may represent a significantly later time than the segment's - /// associated seek position. - /// - /// The time at which this segment started relative to the beginning of the audio. - /// The time at which this segment ended relative to the beginning of the audio. - /// The text that was part of this audio segment. - /// The token IDs matching the text in this audio segment. - /// The temperature score associated with this audio segment. - /// The average log probability associated with this audio segment. - /// The compression ratio of this audio segment. - /// The probability of no speech detection within this audio segment. - /// Keeps track of any properties unknown to the library. - internal AudioSegment(long id, long seek, TimeSpan start, TimeSpan end, string text, IReadOnlyList tokens, double temperature, double avgLogprob, double compressionRatio, double noSpeechProb, IDictionary serializedAdditionalRawData) - { - Id = id; - Seek = seek; - Start = start; - End = end; - Text = text; - Tokens = tokens; - Temperature = temperature; - AvgLogprob = avgLogprob; - CompressionRatio = compressionRatio; - NoSpeechProb = noSpeechProb; - _serializedAdditionalRawData = serializedAdditionalRawData; - } - - /// Initializes a new instance of for deserialization. - internal AudioSegment() - { - } - - /// The zero-based index of this segment. - public long Id { get; } - /// - /// The seek position associated with the processing of this audio segment. Seek positions are - /// expressed as hundredths of seconds. The model may process several segments from a single seek - /// position, so while the seek position will never represent a later time than the segment's - /// start, the segment's start may represent a significantly later time than the segment's - /// associated seek position. - /// - public long Seek { get; } - /// The time at which this segment started relative to the beginning of the audio. - public TimeSpan Start { get; } - /// The time at which this segment ended relative to the beginning of the audio. - public TimeSpan End { get; } - /// The text that was part of this audio segment. - public string Text { get; } - /// The token IDs matching the text in this audio segment. - public IReadOnlyList Tokens { get; } - /// The temperature score associated with this audio segment. - public double Temperature { get; } - /// The average log probability associated with this audio segment. - public double AvgLogprob { get; } - /// The compression ratio of this audio segment. - public double CompressionRatio { get; } - /// The probability of no speech detection within this audio segment. - public double NoSpeechProb { get; } - } -} diff --git a/.dotnet/src/Generated/Models/ChatCompletionTokenLogprob.cs b/.dotnet/src/Generated/Models/ChatCompletionTokenLogprob.cs index 08702fac6..c731d6cfb 100644 --- a/.dotnet/src/Generated/Models/ChatCompletionTokenLogprob.cs +++ b/.dotnet/src/Generated/Models/ChatCompletionTokenLogprob.cs @@ -44,7 +44,10 @@ internal partial class ChatCompletionTokenLogprob /// Initializes a new instance of . /// The token. - /// The log probability of this token. + /// + /// The log probability of this token, if it is within the top 20 most likely tokens. Otherwise, + /// the value `-9999.0` is used to signify that the token is very unlikely. + /// /// /// A list of integers representing the UTF-8 bytes representation of the token. Useful in /// instances where characters are represented by multiple tokens and their byte representations @@ -69,7 +72,10 @@ internal ChatCompletionTokenLogprob(string token, double logprob, IEnumerable Initializes a new instance of . /// The token. - /// The log probability of this token. + /// + /// The log probability of this token, if it is within the top 20 most likely tokens. Otherwise, + /// the value `-9999.0` is used to signify that the token is very unlikely. + /// /// /// A list of integers representing the UTF-8 bytes representation of the token. Useful in /// instances where characters are represented by multiple tokens and their byte representations @@ -97,7 +103,10 @@ internal ChatCompletionTokenLogprob() /// The token. public string Token { get; } - /// The log probability of this token. + /// + /// The log probability of this token, if it is within the top 20 most likely tokens. Otherwise, + /// the value `-9999.0` is used to signify that the token is very unlikely. + /// public double Logprob { get; } /// /// A list of integers representing the UTF-8 bytes representation of the token. Useful in diff --git a/.dotnet/src/Generated/Models/CreateChatCompletionRequest.cs b/.dotnet/src/Generated/Models/CreateChatCompletionRequest.cs index 0cab891f8..4baad9d82 100644 --- a/.dotnet/src/Generated/Models/CreateChatCompletionRequest.cs +++ b/.dotnet/src/Generated/Models/CreateChatCompletionRequest.cs @@ -94,7 +94,7 @@ public CreateChatCompletionRequest(IEnumerable messages, CreateChatC /// currently not available on the `gpt-4-vision-preview` model. /// /// - /// An integer between 0 and 5 specifying the number of most likely tokens to return at each token + /// An integer between 0 and 20 specifying the number of most likely tokens to return at each token /// position, each with an associated log probability. `logprobs` must be set to `true` if this /// parameter is used. /// @@ -118,7 +118,8 @@ public CreateChatCompletionRequest(IEnumerable messages, CreateChatC /// /// /// An object specifying the format that the model must output. Compatible with - /// [GPT-4 Turbo](/docs/models/gpt-4-and-gpt-4-turbo) and `gpt-3.5-turbo-1106`. + /// [GPT-4 Turbo](/docs/models/gpt-4-and-gpt-4-turbo) and all GPT-3.5 Turbo models newer than + /// `gpt-3.5-turbo-1106`. /// /// Setting to `{ "type": "json_object" }` enables JSON mode, which guarantees the message the /// model generates is valid JSON. @@ -277,7 +278,7 @@ internal CreateChatCompletionRequest() /// public bool? Logprobs { get; set; } /// - /// An integer between 0 and 5 specifying the number of most likely tokens to return at each token + /// An integer between 0 and 20 specifying the number of most likely tokens to return at each token /// position, each with an associated log probability. `logprobs` must be set to `true` if this /// parameter is used. /// @@ -305,7 +306,8 @@ internal CreateChatCompletionRequest() public double? PresencePenalty { get; set; } /// /// An object specifying the format that the model must output. Compatible with - /// [GPT-4 Turbo](/docs/models/gpt-4-and-gpt-4-turbo) and `gpt-3.5-turbo-1106`. + /// [GPT-4 Turbo](/docs/models/gpt-4-and-gpt-4-turbo) and all GPT-3.5 Turbo models newer than + /// `gpt-3.5-turbo-1106`. /// /// Setting to `{ "type": "json_object" }` enables JSON mode, which guarantees the message the /// model generates is valid JSON. diff --git a/.dotnet/src/Generated/Models/CreateChatCompletionRequestModel.cs b/.dotnet/src/Generated/Models/CreateChatCompletionRequestModel.cs index a646ff7a2..24c884479 100644 --- a/.dotnet/src/Generated/Models/CreateChatCompletionRequestModel.cs +++ b/.dotnet/src/Generated/Models/CreateChatCompletionRequestModel.cs @@ -32,6 +32,7 @@ public CreateChatCompletionRequestModel(string value) private const string Gpt35Turbo0301Value = "gpt-3.5-turbo-0301"; private const string Gpt35Turbo0613Value = "gpt-3.5-turbo-0613"; private const string Gpt35Turbo1106Value = "gpt-3.5-turbo-1106"; + private const string Gpt35Turbo0125Value = "gpt-3.5-turbo-0125"; private const string Gpt35Turbo16k0613Value = "gpt-3.5-turbo-16k-0613"; /// gpt-4-0125-preview. @@ -64,6 +65,8 @@ public CreateChatCompletionRequestModel(string value) public static CreateChatCompletionRequestModel Gpt35Turbo0613 { get; } = new CreateChatCompletionRequestModel(Gpt35Turbo0613Value); /// gpt-3.5-turbo-1106. public static CreateChatCompletionRequestModel Gpt35Turbo1106 { get; } = new CreateChatCompletionRequestModel(Gpt35Turbo1106Value); + /// gpt-3.5-turbo-0125. + public static CreateChatCompletionRequestModel Gpt35Turbo0125 { get; } = new CreateChatCompletionRequestModel(Gpt35Turbo0125Value); /// gpt-3.5-turbo-16k-0613. public static CreateChatCompletionRequestModel Gpt35Turbo16k0613 { get; } = new CreateChatCompletionRequestModel(Gpt35Turbo16k0613Value); /// Determines if two values are the same. diff --git a/.dotnet/src/Generated/Models/CreateImageRequest.cs b/.dotnet/src/Generated/Models/CreateImageRequest.cs index d0e1f19b6..9145ad3bc 100644 --- a/.dotnet/src/Generated/Models/CreateImageRequest.cs +++ b/.dotnet/src/Generated/Models/CreateImageRequest.cs @@ -68,7 +68,10 @@ public CreateImageRequest(string prompt) /// The quality of the image that will be generated. `hd` creates images with finer details and /// greater consistency across the image. This param is only supported for `dall-e-3`. /// - /// The format in which the generated images are returned. Must be one of `url` or `b64_json`. + /// + /// The format in which the generated images are returned. Must be one of `url` or `b64_json`. URLs + /// are only valid for 60 minutes after the image has been generated. + /// /// /// The size of the generated images. Must be one of `256x256`, `512x512`, or `1024x1024` for /// `dall-e-2`. Must be one of `1024x1024`, `1792x1024`, or `1024x1792` for `dall-e-3` models. @@ -118,7 +121,10 @@ internal CreateImageRequest() /// greater consistency across the image. This param is only supported for `dall-e-3`. /// public CreateImageRequestQuality? Quality { get; set; } - /// The format in which the generated images are returned. Must be one of `url` or `b64_json`. + /// + /// The format in which the generated images are returned. Must be one of `url` or `b64_json`. URLs + /// are only valid for 60 minutes after the image has been generated. + /// public CreateImageRequestResponseFormat? ResponseFormat { get; set; } /// /// The size of the generated images. Must be one of `256x256`, `512x512`, or `1024x1024` for diff --git a/.dotnet/src/Generated/Models/CreateModerationResponse.cs b/.dotnet/src/Generated/Models/CreateModerationResponse.cs index aaacf1180..7af5be06a 100644 --- a/.dotnet/src/Generated/Models/CreateModerationResponse.cs +++ b/.dotnet/src/Generated/Models/CreateModerationResponse.cs @@ -7,7 +7,7 @@ namespace OpenAI.Internal.Models { - /// Represents policy compliance report by OpenAI's content moderation model against a given input. + /// Represents if a given text input is potentially harmful. internal partial class CreateModerationResponse { /// diff --git a/.dotnet/src/Generated/Models/CreateModerationResponseResult.cs b/.dotnet/src/Generated/Models/CreateModerationResponseResult.cs index e9ccb59c9..1d1a29e90 100644 --- a/.dotnet/src/Generated/Models/CreateModerationResponseResult.cs +++ b/.dotnet/src/Generated/Models/CreateModerationResponseResult.cs @@ -42,7 +42,7 @@ internal partial class CreateModerationResponseResult private IDictionary _serializedAdditionalRawData; /// Initializes a new instance of . - /// Whether the content violates [OpenAI's usage policies](/policies/usage-policies). + /// Whether any of the below categories are flagged. /// A list of the categories, and whether they are flagged or not. /// A list of the categories along with their scores as predicted by model. /// or is null. @@ -57,7 +57,7 @@ internal CreateModerationResponseResult(bool flagged, CreateModerationResponseRe } /// Initializes a new instance of . - /// Whether the content violates [OpenAI's usage policies](/policies/usage-policies). + /// Whether any of the below categories are flagged. /// A list of the categories, and whether they are flagged or not. /// A list of the categories along with their scores as predicted by model. /// Keeps track of any properties unknown to the library. @@ -74,7 +74,7 @@ internal CreateModerationResponseResult() { } - /// Whether the content violates [OpenAI's usage policies](/policies/usage-policies). + /// Whether any of the below categories are flagged. public bool Flagged { get; } /// A list of the categories, and whether they are flagged or not. public CreateModerationResponseResultCategories Categories { get; } diff --git a/.dotnet/src/Generated/Models/CreateSpeechRequest.cs b/.dotnet/src/Generated/Models/CreateSpeechRequest.cs index 591623c69..88233f1a7 100644 --- a/.dotnet/src/Generated/Models/CreateSpeechRequest.cs +++ b/.dotnet/src/Generated/Models/CreateSpeechRequest.cs @@ -67,7 +67,7 @@ public CreateSpeechRequest(CreateSpeechRequestModel model, string input, CreateS /// `onyx`, `nova`, and `shimmer`. Previews of the voices are available in the /// [Text to speech guide](/docs/guides/text-to-speech/voice-options). /// - /// The format to audio in. Supported formats are `mp3`, `opus`, `aac`, and `flac`. + /// The format to audio in. Supported formats are `mp3`, `opus`, `aac`, `flac`, `wav`, and `pcm`. /// The speed of the generated audio. Select a value from `0.25` to `4.0`. `1.0` is the default. /// Keeps track of any properties unknown to the library. internal CreateSpeechRequest(CreateSpeechRequestModel model, string input, CreateSpeechRequestVoice voice, CreateSpeechRequestResponseFormat? responseFormat, double? speed, IDictionary serializedAdditionalRawData) @@ -95,7 +95,7 @@ internal CreateSpeechRequest() /// [Text to speech guide](/docs/guides/text-to-speech/voice-options). /// public CreateSpeechRequestVoice Voice { get; } - /// The format to audio in. Supported formats are `mp3`, `opus`, `aac`, and `flac`. + /// The format to audio in. Supported formats are `mp3`, `opus`, `aac`, `flac`, `wav`, and `pcm`. public CreateSpeechRequestResponseFormat? ResponseFormat { get; set; } /// The speed of the generated audio. Select a value from `0.25` to `4.0`. `1.0` is the default. public double? Speed { get; set; } diff --git a/.dotnet/src/Generated/Models/CreateSpeechRequestResponseFormat.cs b/.dotnet/src/Generated/Models/CreateSpeechRequestResponseFormat.cs index 186f59cc2..4cc80f2db 100644 --- a/.dotnet/src/Generated/Models/CreateSpeechRequestResponseFormat.cs +++ b/.dotnet/src/Generated/Models/CreateSpeechRequestResponseFormat.cs @@ -21,6 +21,8 @@ public CreateSpeechRequestResponseFormat(string value) private const string OpusValue = "opus"; private const string AacValue = "aac"; private const string FlacValue = "flac"; + private const string WavValue = "wav"; + private const string PcmValue = "pcm"; /// mp3. public static CreateSpeechRequestResponseFormat Mp3 { get; } = new CreateSpeechRequestResponseFormat(Mp3Value); @@ -30,6 +32,10 @@ public CreateSpeechRequestResponseFormat(string value) public static CreateSpeechRequestResponseFormat Aac { get; } = new CreateSpeechRequestResponseFormat(AacValue); /// flac. public static CreateSpeechRequestResponseFormat Flac { get; } = new CreateSpeechRequestResponseFormat(FlacValue); + /// wav. + public static CreateSpeechRequestResponseFormat Wav { get; } = new CreateSpeechRequestResponseFormat(WavValue); + /// pcm. + public static CreateSpeechRequestResponseFormat Pcm { get; } = new CreateSpeechRequestResponseFormat(PcmValue); /// Determines if two values are the same. public static bool operator ==(CreateSpeechRequestResponseFormat left, CreateSpeechRequestResponseFormat right) => left.Equals(right); /// Determines if two values are not the same. diff --git a/.dotnet/src/Generated/Models/CreateTranscriptionRequest.Serialization.cs b/.dotnet/src/Generated/Models/CreateTranscriptionRequest.Serialization.cs index bd02c679e..50d4a9afb 100644 --- a/.dotnet/src/Generated/Models/CreateTranscriptionRequest.Serialization.cs +++ b/.dotnet/src/Generated/Models/CreateTranscriptionRequest.Serialization.cs @@ -45,6 +45,28 @@ void IJsonModel.Write(Utf8JsonWriter writer, ModelRe writer.WritePropertyName("temperature"u8); writer.WriteNumberValue(Temperature.Value); } + if (Optional.IsCollectionDefined(TimestampGranularities)) + { + writer.WritePropertyName("timestamp_granularities"u8); + writer.WriteStartArray(); + foreach (var item in TimestampGranularities) + { + if (item == null) + { + writer.WriteNullValue(); + continue; + } +#if NET6_0_OR_GREATER + writer.WriteRawValue(item); +#else + using (JsonDocument document = JsonDocument.Parse(item)) + { + JsonSerializer.Serialize(writer, document.RootElement); + } +#endif + } + writer.WriteEndArray(); + } if (options.Format != "W" && _serializedAdditionalRawData != null) { foreach (var item in _serializedAdditionalRawData) @@ -89,6 +111,7 @@ internal static CreateTranscriptionRequest DeserializeCreateTranscriptionRequest string prompt = default; CreateTranscriptionRequestResponseFormat? responseFormat = default; double? temperature = default; + IList timestampGranularities = default; IDictionary serializedAdditionalRawData = default; Dictionary additionalPropertiesDictionary = new Dictionary(); foreach (var property in element.EnumerateObject()) @@ -131,6 +154,27 @@ internal static CreateTranscriptionRequest DeserializeCreateTranscriptionRequest temperature = property.Value.GetDouble(); continue; } + if (property.NameEquals("timestamp_granularities"u8)) + { + if (property.Value.ValueKind == JsonValueKind.Null) + { + continue; + } + List array = new List(); + foreach (var item in property.Value.EnumerateArray()) + { + if (item.ValueKind == JsonValueKind.Null) + { + array.Add(null); + } + else + { + array.Add(BinaryData.FromString(item.GetRawText())); + } + } + timestampGranularities = array; + continue; + } if (options.Format != "W") { additionalPropertiesDictionary.Add(property.Name, BinaryData.FromString(property.Value.GetRawText())); @@ -144,6 +188,7 @@ internal static CreateTranscriptionRequest DeserializeCreateTranscriptionRequest prompt, responseFormat, temperature, + timestampGranularities ?? new ChangeTrackingList(), serializedAdditionalRawData); } diff --git a/.dotnet/src/Generated/Models/CreateTranscriptionRequest.cs b/.dotnet/src/Generated/Models/CreateTranscriptionRequest.cs index 0b52b69b5..09a34f48d 100644 --- a/.dotnet/src/Generated/Models/CreateTranscriptionRequest.cs +++ b/.dotnet/src/Generated/Models/CreateTranscriptionRequest.cs @@ -46,7 +46,10 @@ internal partial class CreateTranscriptionRequest /// The audio file object (not file name) to transcribe, in one of these formats: flac, mp3, mp4, /// mpeg, mpga, m4a, ogg, wav, or webm. /// - /// ID of the model to use. Only `whisper-1` is currently available. + /// + /// ID of the model to use. Only `whisper-1` (which is powered by our open source Whisper V2 model) + /// is currently available. + /// /// is null. public CreateTranscriptionRequest(BinaryData file, CreateTranscriptionRequestModel model) { @@ -54,6 +57,7 @@ public CreateTranscriptionRequest(BinaryData file, CreateTranscriptionRequestMod File = file; Model = model; + TimestampGranularities = new ChangeTrackingList(); } /// Initializes a new instance of . @@ -61,7 +65,10 @@ public CreateTranscriptionRequest(BinaryData file, CreateTranscriptionRequestMod /// The audio file object (not file name) to transcribe, in one of these formats: flac, mp3, mp4, /// mpeg, mpga, m4a, ogg, wav, or webm. /// - /// ID of the model to use. Only `whisper-1` is currently available. + /// + /// ID of the model to use. Only `whisper-1` (which is powered by our open source Whisper V2 model) + /// is currently available. + /// /// /// The language of the input audio. Supplying the input language in /// [ISO-639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) format will improve accuracy @@ -81,8 +88,14 @@ public CreateTranscriptionRequest(BinaryData file, CreateTranscriptionRequestMod /// the model will use [log probability](https://en.wikipedia.org/wiki/Log_probability) to /// automatically increase the temperature until certain thresholds are hit. /// + /// + /// The timestamp granularities to populate for this transcription. `response_format` must be set + /// `verbose_json` to use timestamp granularities. Either or both of these options are supported: + /// `word`, or `segment`. Note: There is no additional latency for segment timestamps, but + /// generating word timestamps incurs additional latency. + /// /// Keeps track of any properties unknown to the library. - internal CreateTranscriptionRequest(BinaryData file, CreateTranscriptionRequestModel model, string language, string prompt, CreateTranscriptionRequestResponseFormat? responseFormat, double? temperature, IDictionary serializedAdditionalRawData) + internal CreateTranscriptionRequest(BinaryData file, CreateTranscriptionRequestModel model, string language, string prompt, CreateTranscriptionRequestResponseFormat? responseFormat, double? temperature, IList timestampGranularities, IDictionary serializedAdditionalRawData) { File = file; Model = model; @@ -90,6 +103,7 @@ internal CreateTranscriptionRequest(BinaryData file, CreateTranscriptionRequestM Prompt = prompt; ResponseFormat = responseFormat; Temperature = temperature; + TimestampGranularities = timestampGranularities; _serializedAdditionalRawData = serializedAdditionalRawData; } @@ -116,7 +130,10 @@ internal CreateTranscriptionRequest() /// /// public BinaryData File { get; } - /// ID of the model to use. Only `whisper-1` is currently available. + /// + /// ID of the model to use. Only `whisper-1` (which is powered by our open source Whisper V2 model) + /// is currently available. + /// public CreateTranscriptionRequestModel Model { get; } /// /// The language of the input audio. Supplying the input language in @@ -141,5 +158,39 @@ internal CreateTranscriptionRequest() /// automatically increase the temperature until certain thresholds are hit. /// public double? Temperature { get; set; } + /// + /// The timestamp granularities to populate for this transcription. `response_format` must be set + /// `verbose_json` to use timestamp granularities. Either or both of these options are supported: + /// `word`, or `segment`. Note: There is no additional latency for segment timestamps, but + /// generating word timestamps incurs additional latency. + /// + /// To assign an object to the element of this property use . + /// + /// + /// To assign an already formatted json string to this property use . + /// + /// + /// Examples: + /// + /// + /// BinaryData.FromObjectAsJson("foo") + /// Creates a payload of "foo". + /// + /// + /// BinaryData.FromString("\"foo\"") + /// Creates a payload of "foo". + /// + /// + /// BinaryData.FromObjectAsJson(new { key = "value" }) + /// Creates a payload of { "key": "value" }. + /// + /// + /// BinaryData.FromString("{\"key\": \"value\"}") + /// Creates a payload of { "key": "value" }. + /// + /// + /// + /// + public IList TimestampGranularities { get; } } } diff --git a/.dotnet/src/Generated/Models/CreateTranscriptionResponse.Serialization.cs b/.dotnet/src/Generated/Models/CreateTranscriptionResponseVerboseJson.Serialization.cs similarity index 58% rename from .dotnet/src/Generated/Models/CreateTranscriptionResponse.Serialization.cs rename to .dotnet/src/Generated/Models/CreateTranscriptionResponseVerboseJson.Serialization.cs index 6bf802a18..b3a744abe 100644 --- a/.dotnet/src/Generated/Models/CreateTranscriptionResponse.Serialization.cs +++ b/.dotnet/src/Generated/Models/CreateTranscriptionResponseVerboseJson.Serialization.cs @@ -10,33 +10,34 @@ namespace OpenAI.Internal.Models { - internal partial class CreateTranscriptionResponse : IJsonModel + internal partial class CreateTranscriptionResponseVerboseJson : IJsonModel { - void IJsonModel.Write(Utf8JsonWriter writer, ModelReaderWriterOptions options) + void IJsonModel.Write(Utf8JsonWriter writer, ModelReaderWriterOptions options) { - var format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; + var format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; if (format != "J") { - throw new FormatException($"The model {nameof(CreateTranscriptionResponse)} does not support '{format}' format."); + throw new FormatException($"The model {nameof(CreateTranscriptionResponseVerboseJson)} does not support '{format}' format."); } writer.WriteStartObject(); + writer.WritePropertyName("task"u8); + writer.WriteStringValue(Task.ToString()); + writer.WritePropertyName("language"u8); + writer.WriteStringValue(Language); + writer.WritePropertyName("duration"u8); + writer.WriteNumberValue(Convert.ToInt32(Duration.ToString("%s"))); writer.WritePropertyName("text"u8); writer.WriteStringValue(Text); - if (Optional.IsDefined(Task)) + if (Optional.IsCollectionDefined(Words)) { - writer.WritePropertyName("task"u8); - writer.WriteStringValue(Task.Value.ToString()); - } - if (Optional.IsDefined(Language)) - { - writer.WritePropertyName("language"u8); - writer.WriteStringValue(Language); - } - if (Optional.IsDefined(Duration)) - { - writer.WritePropertyName("duration"u8); - writer.WriteNumberValue(Convert.ToInt32(Duration.Value.ToString("%s"))); + writer.WritePropertyName("words"u8); + writer.WriteStartArray(); + foreach (var item in Words) + { + writer.WriteObjectValue(item); + } + writer.WriteEndArray(); } if (Optional.IsCollectionDefined(Segments)) { @@ -66,19 +67,19 @@ void IJsonModel.Write(Utf8JsonWriter writer, ModelR writer.WriteEndObject(); } - CreateTranscriptionResponse IJsonModel.Create(ref Utf8JsonReader reader, ModelReaderWriterOptions options) + CreateTranscriptionResponseVerboseJson IJsonModel.Create(ref Utf8JsonReader reader, ModelReaderWriterOptions options) { - var format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; + var format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; if (format != "J") { - throw new FormatException($"The model {nameof(CreateTranscriptionResponse)} does not support '{format}' format."); + throw new FormatException($"The model {nameof(CreateTranscriptionResponseVerboseJson)} does not support '{format}' format."); } using JsonDocument document = JsonDocument.ParseValue(ref reader); - return DeserializeCreateTranscriptionResponse(document.RootElement, options); + return DeserializeCreateTranscriptionResponseVerboseJson(document.RootElement, options); } - internal static CreateTranscriptionResponse DeserializeCreateTranscriptionResponse(JsonElement element, ModelReaderWriterOptions options = null) + internal static CreateTranscriptionResponseVerboseJson DeserializeCreateTranscriptionResponseVerboseJson(JsonElement element, ModelReaderWriterOptions options = null) { options ??= new ModelReaderWriterOptions("W"); @@ -86,27 +87,19 @@ internal static CreateTranscriptionResponse DeserializeCreateTranscriptionRespon { return null; } - string text = default; - CreateTranscriptionResponseTask? task = default; + CreateTranscriptionResponseVerboseJsonTask task = default; string language = default; - TimeSpan? duration = default; - IReadOnlyList segments = default; + TimeSpan duration = default; + string text = default; + IReadOnlyList words = default; + IReadOnlyList segments = default; IDictionary serializedAdditionalRawData = default; Dictionary additionalPropertiesDictionary = new Dictionary(); foreach (var property in element.EnumerateObject()) { - if (property.NameEquals("text"u8)) - { - text = property.Value.GetString(); - continue; - } if (property.NameEquals("task"u8)) { - if (property.Value.ValueKind == JsonValueKind.Null) - { - continue; - } - task = new CreateTranscriptionResponseTask(property.Value.GetString()); + task = new CreateTranscriptionResponseVerboseJsonTask(property.Value.GetString()); continue; } if (property.NameEquals("language"u8)) @@ -115,12 +108,27 @@ internal static CreateTranscriptionResponse DeserializeCreateTranscriptionRespon continue; } if (property.NameEquals("duration"u8)) + { + duration = TimeSpan.FromSeconds(property.Value.GetInt32()); + continue; + } + if (property.NameEquals("text"u8)) + { + text = property.Value.GetString(); + continue; + } + if (property.NameEquals("words"u8)) { if (property.Value.ValueKind == JsonValueKind.Null) { continue; } - duration = TimeSpan.FromSeconds(property.Value.GetInt32()); + List array = new List(); + foreach (var item in property.Value.EnumerateArray()) + { + array.Add(TranscriptionWord.DeserializeTranscriptionWord(item, options)); + } + words = array; continue; } if (property.NameEquals("segments"u8)) @@ -129,10 +137,10 @@ internal static CreateTranscriptionResponse DeserializeCreateTranscriptionRespon { continue; } - List array = new List(); + List array = new List(); foreach (var item in property.Value.EnumerateArray()) { - array.Add(AudioSegment.DeserializeAudioSegment(item, options)); + array.Add(TranscriptionSegment.DeserializeTranscriptionSegment(item, options)); } segments = array; continue; @@ -143,52 +151,53 @@ internal static CreateTranscriptionResponse DeserializeCreateTranscriptionRespon } } serializedAdditionalRawData = additionalPropertiesDictionary; - return new CreateTranscriptionResponse( - text, + return new CreateTranscriptionResponseVerboseJson( task, language, duration, - segments ?? new ChangeTrackingList(), + text, + words ?? new ChangeTrackingList(), + segments ?? new ChangeTrackingList(), serializedAdditionalRawData); } - BinaryData IPersistableModel.Write(ModelReaderWriterOptions options) + BinaryData IPersistableModel.Write(ModelReaderWriterOptions options) { - var format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; + var format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; switch (format) { case "J": return ModelReaderWriter.Write(this, options); default: - throw new FormatException($"The model {nameof(CreateTranscriptionResponse)} does not support '{options.Format}' format."); + throw new FormatException($"The model {nameof(CreateTranscriptionResponseVerboseJson)} does not support '{options.Format}' format."); } } - CreateTranscriptionResponse IPersistableModel.Create(BinaryData data, ModelReaderWriterOptions options) + CreateTranscriptionResponseVerboseJson IPersistableModel.Create(BinaryData data, ModelReaderWriterOptions options) { - var format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; + var format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; switch (format) { case "J": { using JsonDocument document = JsonDocument.Parse(data); - return DeserializeCreateTranscriptionResponse(document.RootElement, options); + return DeserializeCreateTranscriptionResponseVerboseJson(document.RootElement, options); } default: - throw new FormatException($"The model {nameof(CreateTranscriptionResponse)} does not support '{options.Format}' format."); + throw new FormatException($"The model {nameof(CreateTranscriptionResponseVerboseJson)} does not support '{options.Format}' format."); } } - string IPersistableModel.GetFormatFromOptions(ModelReaderWriterOptions options) => "J"; + string IPersistableModel.GetFormatFromOptions(ModelReaderWriterOptions options) => "J"; /// Deserializes the model from a raw response. /// The result to deserialize the model from. - internal static CreateTranscriptionResponse FromResponse(PipelineResponse response) + internal static CreateTranscriptionResponseVerboseJson FromResponse(PipelineResponse response) { using var document = JsonDocument.Parse(response.Content); - return DeserializeCreateTranscriptionResponse(document.RootElement); + return DeserializeCreateTranscriptionResponseVerboseJson(document.RootElement); } /// Convert into a Utf8JsonRequestBody. diff --git a/.dotnet/src/Generated/Models/CreateTranscriptionResponseVerboseJson.cs b/.dotnet/src/Generated/Models/CreateTranscriptionResponseVerboseJson.cs new file mode 100644 index 000000000..2664ad2be --- /dev/null +++ b/.dotnet/src/Generated/Models/CreateTranscriptionResponseVerboseJson.cs @@ -0,0 +1,99 @@ +// + +using System; +using System.Collections.Generic; +using OpenAI; + +namespace OpenAI.Internal.Models +{ + /// Represents a verbose json transcription response returned by model, based on the provided input. + internal partial class CreateTranscriptionResponseVerboseJson + { + /// + /// Keeps track of any properties unknown to the library. + /// + /// To assign an object to the value of this property use . + /// + /// + /// To assign an already formatted json string to this property use . + /// + /// + /// Examples: + /// + /// + /// BinaryData.FromObjectAsJson("foo") + /// Creates a payload of "foo". + /// + /// + /// BinaryData.FromString("\"foo\"") + /// Creates a payload of "foo". + /// + /// + /// BinaryData.FromObjectAsJson(new { key = "value" }) + /// Creates a payload of { "key": "value" }. + /// + /// + /// BinaryData.FromString("{\"key\": \"value\"}") + /// Creates a payload of { "key": "value" }. + /// + /// + /// + /// + private IDictionary _serializedAdditionalRawData; + + /// Initializes a new instance of . + /// The language of the input audio. + /// The duration of the input audio. + /// The transcribed text. + /// or is null. + internal CreateTranscriptionResponseVerboseJson(string language, TimeSpan duration, string text) + { + Argument.AssertNotNull(language, nameof(language)); + Argument.AssertNotNull(text, nameof(text)); + + Language = language; + Duration = duration; + Text = text; + Words = new ChangeTrackingList(); + Segments = new ChangeTrackingList(); + } + + /// Initializes a new instance of . + /// The task label. + /// The language of the input audio. + /// The duration of the input audio. + /// The transcribed text. + /// Extracted words and their corresponding timestamps. + /// Segments of the transcribed text and their corresponding details. + /// Keeps track of any properties unknown to the library. + internal CreateTranscriptionResponseVerboseJson(CreateTranscriptionResponseVerboseJsonTask task, string language, TimeSpan duration, string text, IReadOnlyList words, IReadOnlyList segments, IDictionary serializedAdditionalRawData) + { + Task = task; + Language = language; + Duration = duration; + Text = text; + Words = words; + Segments = segments; + _serializedAdditionalRawData = serializedAdditionalRawData; + } + + /// Initializes a new instance of for deserialization. + internal CreateTranscriptionResponseVerboseJson() + { + } + + /// The task label. + public CreateTranscriptionResponseVerboseJsonTask Task { get; } = CreateTranscriptionResponseVerboseJsonTask.Transcribe; + + /// The language of the input audio. + public string Language { get; } + /// The duration of the input audio. + public TimeSpan Duration { get; } + /// The transcribed text. + public string Text { get; } + /// Extracted words and their corresponding timestamps. + public IReadOnlyList Words { get; } + /// Segments of the transcribed text and their corresponding details. + public IReadOnlyList Segments { get; } + } +} diff --git a/.dotnet/src/Generated/Models/CreateTranscriptionResponseTask.cs b/.dotnet/src/Generated/Models/CreateTranscriptionResponseVerboseJsonTask.cs similarity index 55% rename from .dotnet/src/Generated/Models/CreateTranscriptionResponseTask.cs rename to .dotnet/src/Generated/Models/CreateTranscriptionResponseVerboseJsonTask.cs index 167892816..68e526abd 100644 --- a/.dotnet/src/Generated/Models/CreateTranscriptionResponseTask.cs +++ b/.dotnet/src/Generated/Models/CreateTranscriptionResponseVerboseJsonTask.cs @@ -5,14 +5,14 @@ namespace OpenAI.Internal.Models { - /// The CreateTranscriptionResponse_task. - internal readonly partial struct CreateTranscriptionResponseTask : IEquatable + /// The CreateTranscriptionResponseVerboseJson_task. + internal readonly partial struct CreateTranscriptionResponseVerboseJsonTask : IEquatable { private readonly string _value; - /// Initializes a new instance of . + /// Initializes a new instance of . /// is null. - public CreateTranscriptionResponseTask(string value) + public CreateTranscriptionResponseVerboseJsonTask(string value) { _value = value ?? throw new ArgumentNullException(nameof(value)); } @@ -20,19 +20,19 @@ public CreateTranscriptionResponseTask(string value) private const string TranscribeValue = "transcribe"; /// transcribe. - public static CreateTranscriptionResponseTask Transcribe { get; } = new CreateTranscriptionResponseTask(TranscribeValue); - /// Determines if two values are the same. - public static bool operator ==(CreateTranscriptionResponseTask left, CreateTranscriptionResponseTask right) => left.Equals(right); - /// Determines if two values are not the same. - public static bool operator !=(CreateTranscriptionResponseTask left, CreateTranscriptionResponseTask right) => !left.Equals(right); - /// Converts a string to a . - public static implicit operator CreateTranscriptionResponseTask(string value) => new CreateTranscriptionResponseTask(value); + public static CreateTranscriptionResponseVerboseJsonTask Transcribe { get; } = new CreateTranscriptionResponseVerboseJsonTask(TranscribeValue); + /// Determines if two values are the same. + public static bool operator ==(CreateTranscriptionResponseVerboseJsonTask left, CreateTranscriptionResponseVerboseJsonTask right) => left.Equals(right); + /// Determines if two values are not the same. + public static bool operator !=(CreateTranscriptionResponseVerboseJsonTask left, CreateTranscriptionResponseVerboseJsonTask right) => !left.Equals(right); + /// Converts a string to a . + public static implicit operator CreateTranscriptionResponseVerboseJsonTask(string value) => new CreateTranscriptionResponseVerboseJsonTask(value); /// [EditorBrowsable(EditorBrowsableState.Never)] - public override bool Equals(object obj) => obj is CreateTranscriptionResponseTask other && Equals(other); + public override bool Equals(object obj) => obj is CreateTranscriptionResponseVerboseJsonTask other && Equals(other); /// - public bool Equals(CreateTranscriptionResponseTask other) => string.Equals(_value, other._value, StringComparison.InvariantCultureIgnoreCase); + public bool Equals(CreateTranscriptionResponseVerboseJsonTask other) => string.Equals(_value, other._value, StringComparison.InvariantCultureIgnoreCase); /// [EditorBrowsable(EditorBrowsableState.Never)] diff --git a/.dotnet/src/Generated/Models/CreateTranslationRequest.cs b/.dotnet/src/Generated/Models/CreateTranslationRequest.cs index 848333e45..acc2f53f4 100644 --- a/.dotnet/src/Generated/Models/CreateTranslationRequest.cs +++ b/.dotnet/src/Generated/Models/CreateTranslationRequest.cs @@ -46,7 +46,10 @@ internal partial class CreateTranslationRequest /// The audio file object (not file name) to translate, in one of these formats: flac, mp3, mp4, /// mpeg, mpga, m4a, ogg, wav, or webm. /// - /// ID of the model to use. Only `whisper-1` is currently available. + /// + /// ID of the model to use. Only `whisper-1` (which is powered by our open source Whisper V2 model) + /// is currently available. + /// /// is null. public CreateTranslationRequest(BinaryData file, CreateTranslationRequestModel model) { @@ -61,7 +64,10 @@ public CreateTranslationRequest(BinaryData file, CreateTranslationRequestModel m /// The audio file object (not file name) to translate, in one of these formats: flac, mp3, mp4, /// mpeg, mpga, m4a, ogg, wav, or webm. /// - /// ID of the model to use. Only `whisper-1` is currently available. + /// + /// ID of the model to use. Only `whisper-1` (which is powered by our open source Whisper V2 model) + /// is currently available. + /// /// /// An optional text to guide the model's style or continue a previous audio segment. The /// [prompt](/docs/guides/speech-to-text/prompting) should match the audio language. @@ -110,7 +116,10 @@ internal CreateTranslationRequest() /// /// public BinaryData File { get; } - /// ID of the model to use. Only `whisper-1` is currently available. + /// + /// ID of the model to use. Only `whisper-1` (which is powered by our open source Whisper V2 model) + /// is currently available. + /// public CreateTranslationRequestModel Model { get; } /// /// An optional text to guide the model's style or continue a previous audio segment. The diff --git a/.dotnet/src/Generated/Models/CreateTranslationResponse.cs b/.dotnet/src/Generated/Models/CreateTranslationResponse.cs deleted file mode 100644 index 1cc325c68..000000000 --- a/.dotnet/src/Generated/Models/CreateTranslationResponse.cs +++ /dev/null @@ -1,94 +0,0 @@ -// - -using System; -using System.Collections.Generic; -using OpenAI; - -namespace OpenAI.Internal.Models -{ - /// The CreateTranslationResponse. - internal partial class CreateTranslationResponse - { - /// - /// Keeps track of any properties unknown to the library. - /// - /// To assign an object to the value of this property use . - /// - /// - /// To assign an already formatted json string to this property use . - /// - /// - /// Examples: - /// - /// - /// BinaryData.FromObjectAsJson("foo") - /// Creates a payload of "foo". - /// - /// - /// BinaryData.FromString("\"foo\"") - /// Creates a payload of "foo". - /// - /// - /// BinaryData.FromObjectAsJson(new { key = "value" }) - /// Creates a payload of { "key": "value" }. - /// - /// - /// BinaryData.FromString("{\"key\": \"value\"}") - /// Creates a payload of { "key": "value" }. - /// - /// - /// - /// - private IDictionary _serializedAdditionalRawData; - - /// Initializes a new instance of . - /// The translated text for the provided audio data. - /// is null. - internal CreateTranslationResponse(string text) - { - Argument.AssertNotNull(text, nameof(text)); - - Text = text; - Segments = new ChangeTrackingList(); - } - - /// Initializes a new instance of . - /// The translated text for the provided audio data. - /// The label that describes which operation type generated the accompanying response data. - /// The spoken language that was detected in the audio data. - /// The total duration of the audio processed to produce accompanying translation information. - /// - /// A collection of information about the timing, probabilities, and other detail of each processed - /// audio segment. - /// - /// Keeps track of any properties unknown to the library. - internal CreateTranslationResponse(string text, CreateTranslationResponseTask? task, string language, TimeSpan? duration, IReadOnlyList segments, IDictionary serializedAdditionalRawData) - { - Text = text; - Task = task; - Language = language; - Duration = duration; - Segments = segments; - _serializedAdditionalRawData = serializedAdditionalRawData; - } - - /// Initializes a new instance of for deserialization. - internal CreateTranslationResponse() - { - } - - /// The translated text for the provided audio data. - public string Text { get; } - /// The label that describes which operation type generated the accompanying response data. - public CreateTranslationResponseTask? Task { get; } - /// The spoken language that was detected in the audio data. - public string Language { get; } - /// The total duration of the audio processed to produce accompanying translation information. - public TimeSpan? Duration { get; } - /// - /// A collection of information about the timing, probabilities, and other detail of each processed - /// audio segment. - /// - public IReadOnlyList Segments { get; } - } -} diff --git a/.dotnet/src/Generated/Models/CreateTranslationResponse.Serialization.cs b/.dotnet/src/Generated/Models/CreateTranslationResponseVerboseJson.Serialization.cs similarity index 61% rename from .dotnet/src/Generated/Models/CreateTranslationResponse.Serialization.cs rename to .dotnet/src/Generated/Models/CreateTranslationResponseVerboseJson.Serialization.cs index d59ef520b..15aa56500 100644 --- a/.dotnet/src/Generated/Models/CreateTranslationResponse.Serialization.cs +++ b/.dotnet/src/Generated/Models/CreateTranslationResponseVerboseJson.Serialization.cs @@ -10,34 +10,25 @@ namespace OpenAI.Internal.Models { - internal partial class CreateTranslationResponse : IJsonModel + internal partial class CreateTranslationResponseVerboseJson : IJsonModel { - void IJsonModel.Write(Utf8JsonWriter writer, ModelReaderWriterOptions options) + void IJsonModel.Write(Utf8JsonWriter writer, ModelReaderWriterOptions options) { - var format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; + var format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; if (format != "J") { - throw new FormatException($"The model {nameof(CreateTranslationResponse)} does not support '{format}' format."); + throw new FormatException($"The model {nameof(CreateTranslationResponseVerboseJson)} does not support '{format}' format."); } writer.WriteStartObject(); + writer.WritePropertyName("task"u8); + writer.WriteStringValue(Task.ToString()); + writer.WritePropertyName("language"u8); + writer.WriteStringValue(Language); + writer.WritePropertyName("duration"u8); + writer.WriteNumberValue(Convert.ToInt32(Duration.ToString("%s"))); writer.WritePropertyName("text"u8); writer.WriteStringValue(Text); - if (Optional.IsDefined(Task)) - { - writer.WritePropertyName("task"u8); - writer.WriteStringValue(Task.Value.ToString()); - } - if (Optional.IsDefined(Language)) - { - writer.WritePropertyName("language"u8); - writer.WriteStringValue(Language); - } - if (Optional.IsDefined(Duration)) - { - writer.WritePropertyName("duration"u8); - writer.WriteNumberValue(Convert.ToInt32(Duration.Value.ToString("%s"))); - } if (Optional.IsCollectionDefined(Segments)) { writer.WritePropertyName("segments"u8); @@ -66,19 +57,19 @@ void IJsonModel.Write(Utf8JsonWriter writer, ModelRea writer.WriteEndObject(); } - CreateTranslationResponse IJsonModel.Create(ref Utf8JsonReader reader, ModelReaderWriterOptions options) + CreateTranslationResponseVerboseJson IJsonModel.Create(ref Utf8JsonReader reader, ModelReaderWriterOptions options) { - var format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; + var format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; if (format != "J") { - throw new FormatException($"The model {nameof(CreateTranslationResponse)} does not support '{format}' format."); + throw new FormatException($"The model {nameof(CreateTranslationResponseVerboseJson)} does not support '{format}' format."); } using JsonDocument document = JsonDocument.ParseValue(ref reader); - return DeserializeCreateTranslationResponse(document.RootElement, options); + return DeserializeCreateTranslationResponseVerboseJson(document.RootElement, options); } - internal static CreateTranslationResponse DeserializeCreateTranslationResponse(JsonElement element, ModelReaderWriterOptions options = null) + internal static CreateTranslationResponseVerboseJson DeserializeCreateTranslationResponseVerboseJson(JsonElement element, ModelReaderWriterOptions options = null) { options ??= new ModelReaderWriterOptions("W"); @@ -86,27 +77,18 @@ internal static CreateTranslationResponse DeserializeCreateTranslationResponse(J { return null; } - string text = default; - CreateTranslationResponseTask? task = default; + CreateTranslationResponseVerboseJsonTask task = default; string language = default; - TimeSpan? duration = default; - IReadOnlyList segments = default; + TimeSpan duration = default; + string text = default; + IReadOnlyList segments = default; IDictionary serializedAdditionalRawData = default; Dictionary additionalPropertiesDictionary = new Dictionary(); foreach (var property in element.EnumerateObject()) { - if (property.NameEquals("text"u8)) - { - text = property.Value.GetString(); - continue; - } if (property.NameEquals("task"u8)) { - if (property.Value.ValueKind == JsonValueKind.Null) - { - continue; - } - task = new CreateTranslationResponseTask(property.Value.GetString()); + task = new CreateTranslationResponseVerboseJsonTask(property.Value.GetString()); continue; } if (property.NameEquals("language"u8)) @@ -116,23 +98,24 @@ internal static CreateTranslationResponse DeserializeCreateTranslationResponse(J } if (property.NameEquals("duration"u8)) { - if (property.Value.ValueKind == JsonValueKind.Null) - { - continue; - } duration = TimeSpan.FromSeconds(property.Value.GetInt32()); continue; } + if (property.NameEquals("text"u8)) + { + text = property.Value.GetString(); + continue; + } if (property.NameEquals("segments"u8)) { if (property.Value.ValueKind == JsonValueKind.Null) { continue; } - List array = new List(); + List array = new List(); foreach (var item in property.Value.EnumerateArray()) { - array.Add(AudioSegment.DeserializeAudioSegment(item, options)); + array.Add(TranscriptionSegment.DeserializeTranscriptionSegment(item, options)); } segments = array; continue; @@ -143,52 +126,52 @@ internal static CreateTranslationResponse DeserializeCreateTranslationResponse(J } } serializedAdditionalRawData = additionalPropertiesDictionary; - return new CreateTranslationResponse( - text, + return new CreateTranslationResponseVerboseJson( task, language, duration, - segments ?? new ChangeTrackingList(), + text, + segments ?? new ChangeTrackingList(), serializedAdditionalRawData); } - BinaryData IPersistableModel.Write(ModelReaderWriterOptions options) + BinaryData IPersistableModel.Write(ModelReaderWriterOptions options) { - var format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; + var format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; switch (format) { case "J": return ModelReaderWriter.Write(this, options); default: - throw new FormatException($"The model {nameof(CreateTranslationResponse)} does not support '{options.Format}' format."); + throw new FormatException($"The model {nameof(CreateTranslationResponseVerboseJson)} does not support '{options.Format}' format."); } } - CreateTranslationResponse IPersistableModel.Create(BinaryData data, ModelReaderWriterOptions options) + CreateTranslationResponseVerboseJson IPersistableModel.Create(BinaryData data, ModelReaderWriterOptions options) { - var format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; + var format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; switch (format) { case "J": { using JsonDocument document = JsonDocument.Parse(data); - return DeserializeCreateTranslationResponse(document.RootElement, options); + return DeserializeCreateTranslationResponseVerboseJson(document.RootElement, options); } default: - throw new FormatException($"The model {nameof(CreateTranslationResponse)} does not support '{options.Format}' format."); + throw new FormatException($"The model {nameof(CreateTranslationResponseVerboseJson)} does not support '{options.Format}' format."); } } - string IPersistableModel.GetFormatFromOptions(ModelReaderWriterOptions options) => "J"; + string IPersistableModel.GetFormatFromOptions(ModelReaderWriterOptions options) => "J"; /// Deserializes the model from a raw response. /// The result to deserialize the model from. - internal static CreateTranslationResponse FromResponse(PipelineResponse response) + internal static CreateTranslationResponseVerboseJson FromResponse(PipelineResponse response) { using var document = JsonDocument.Parse(response.Content); - return DeserializeCreateTranslationResponse(document.RootElement); + return DeserializeCreateTranslationResponseVerboseJson(document.RootElement); } /// Convert into a Utf8JsonRequestBody. diff --git a/.dotnet/src/Generated/Models/CreateTranscriptionResponse.cs b/.dotnet/src/Generated/Models/CreateTranslationResponseVerboseJson.cs similarity index 50% rename from .dotnet/src/Generated/Models/CreateTranscriptionResponse.cs rename to .dotnet/src/Generated/Models/CreateTranslationResponseVerboseJson.cs index 0fb8162d3..45533fb11 100644 --- a/.dotnet/src/Generated/Models/CreateTranscriptionResponse.cs +++ b/.dotnet/src/Generated/Models/CreateTranslationResponseVerboseJson.cs @@ -6,8 +6,8 @@ namespace OpenAI.Internal.Models { - /// The CreateTranscriptionResponse. - internal partial class CreateTranscriptionResponse + /// The CreateTranslationResponseVerboseJson. + internal partial class CreateTranslationResponseVerboseJson { /// /// Keeps track of any properties unknown to the library. @@ -41,54 +41,54 @@ internal partial class CreateTranscriptionResponse /// private IDictionary _serializedAdditionalRawData; - /// Initializes a new instance of . - /// The transcribed text for the provided audio data. - /// is null. - internal CreateTranscriptionResponse(string text) + /// Initializes a new instance of . + /// The language of the output translation (always `english`). + /// The duration of the input audio. + /// The translated text. + /// or is null. + internal CreateTranslationResponseVerboseJson(string language, TimeSpan duration, string text) { + Argument.AssertNotNull(language, nameof(language)); Argument.AssertNotNull(text, nameof(text)); + Language = language; + Duration = duration; Text = text; - Segments = new ChangeTrackingList(); + Segments = new ChangeTrackingList(); } - /// Initializes a new instance of . - /// The transcribed text for the provided audio data. - /// The label that describes which operation type generated the accompanying response data. - /// The spoken language that was detected in the audio data. - /// The total duration of the audio processed to produce accompanying transcription information. - /// - /// A collection of information about the timing, probabilities, and other detail of each processed - /// audio segment. - /// + /// Initializes a new instance of . + /// The task label. + /// The language of the output translation (always `english`). + /// The duration of the input audio. + /// The translated text. + /// Segments of the translated text and their corresponding details. /// Keeps track of any properties unknown to the library. - internal CreateTranscriptionResponse(string text, CreateTranscriptionResponseTask? task, string language, TimeSpan? duration, IReadOnlyList segments, IDictionary serializedAdditionalRawData) + internal CreateTranslationResponseVerboseJson(CreateTranslationResponseVerboseJsonTask task, string language, TimeSpan duration, string text, IReadOnlyList segments, IDictionary serializedAdditionalRawData) { - Text = text; Task = task; Language = language; Duration = duration; + Text = text; Segments = segments; _serializedAdditionalRawData = serializedAdditionalRawData; } - /// Initializes a new instance of for deserialization. - internal CreateTranscriptionResponse() + /// Initializes a new instance of for deserialization. + internal CreateTranslationResponseVerboseJson() { } - /// The transcribed text for the provided audio data. - public string Text { get; } - /// The label that describes which operation type generated the accompanying response data. - public CreateTranscriptionResponseTask? Task { get; } - /// The spoken language that was detected in the audio data. + /// The task label. + public CreateTranslationResponseVerboseJsonTask Task { get; } = CreateTranslationResponseVerboseJsonTask.Translate; + + /// The language of the output translation (always `english`). public string Language { get; } - /// The total duration of the audio processed to produce accompanying transcription information. - public TimeSpan? Duration { get; } - /// - /// A collection of information about the timing, probabilities, and other detail of each processed - /// audio segment. - /// - public IReadOnlyList Segments { get; } + /// The duration of the input audio. + public TimeSpan Duration { get; } + /// The translated text. + public string Text { get; } + /// Segments of the translated text and their corresponding details. + public IReadOnlyList Segments { get; } } } diff --git a/.dotnet/src/Generated/Models/CreateTranslationResponseTask.cs b/.dotnet/src/Generated/Models/CreateTranslationResponseVerboseJsonTask.cs similarity index 53% rename from .dotnet/src/Generated/Models/CreateTranslationResponseTask.cs rename to .dotnet/src/Generated/Models/CreateTranslationResponseVerboseJsonTask.cs index db1eaf0f3..06dda646b 100644 --- a/.dotnet/src/Generated/Models/CreateTranslationResponseTask.cs +++ b/.dotnet/src/Generated/Models/CreateTranslationResponseVerboseJsonTask.cs @@ -5,14 +5,14 @@ namespace OpenAI.Internal.Models { - /// The CreateTranslationResponse_task. - internal readonly partial struct CreateTranslationResponseTask : IEquatable + /// The CreateTranslationResponseVerboseJson_task. + internal readonly partial struct CreateTranslationResponseVerboseJsonTask : IEquatable { private readonly string _value; - /// Initializes a new instance of . + /// Initializes a new instance of . /// is null. - public CreateTranslationResponseTask(string value) + public CreateTranslationResponseVerboseJsonTask(string value) { _value = value ?? throw new ArgumentNullException(nameof(value)); } @@ -20,19 +20,19 @@ public CreateTranslationResponseTask(string value) private const string TranslateValue = "translate"; /// translate. - public static CreateTranslationResponseTask Translate { get; } = new CreateTranslationResponseTask(TranslateValue); - /// Determines if two values are the same. - public static bool operator ==(CreateTranslationResponseTask left, CreateTranslationResponseTask right) => left.Equals(right); - /// Determines if two values are not the same. - public static bool operator !=(CreateTranslationResponseTask left, CreateTranslationResponseTask right) => !left.Equals(right); - /// Converts a string to a . - public static implicit operator CreateTranslationResponseTask(string value) => new CreateTranslationResponseTask(value); + public static CreateTranslationResponseVerboseJsonTask Translate { get; } = new CreateTranslationResponseVerboseJsonTask(TranslateValue); + /// Determines if two values are the same. + public static bool operator ==(CreateTranslationResponseVerboseJsonTask left, CreateTranslationResponseVerboseJsonTask right) => left.Equals(right); + /// Determines if two values are not the same. + public static bool operator !=(CreateTranslationResponseVerboseJsonTask left, CreateTranslationResponseVerboseJsonTask right) => !left.Equals(right); + /// Converts a string to a . + public static implicit operator CreateTranslationResponseVerboseJsonTask(string value) => new CreateTranslationResponseVerboseJsonTask(value); /// [EditorBrowsable(EditorBrowsableState.Never)] - public override bool Equals(object obj) => obj is CreateTranslationResponseTask other && Equals(other); + public override bool Equals(object obj) => obj is CreateTranslationResponseVerboseJsonTask other && Equals(other); /// - public bool Equals(CreateTranslationResponseTask other) => string.Equals(_value, other._value, StringComparison.InvariantCultureIgnoreCase); + public bool Equals(CreateTranslationResponseVerboseJsonTask other) => string.Equals(_value, other._value, StringComparison.InvariantCultureIgnoreCase); /// [EditorBrowsable(EditorBrowsableState.Never)] diff --git a/.dotnet/src/Generated/Models/RunObjectLastError.cs b/.dotnet/src/Generated/Models/RunObjectLastError.cs index 185f4fcfa..267074aa3 100644 --- a/.dotnet/src/Generated/Models/RunObjectLastError.cs +++ b/.dotnet/src/Generated/Models/RunObjectLastError.cs @@ -42,7 +42,7 @@ internal partial class RunObjectLastError private IDictionary _serializedAdditionalRawData; /// Initializes a new instance of . - /// One of `server_error` or `rate_limit_exceeded`. + /// One of `server_error`, `rate_limit_exceeded`, or `invalid_prompt`. /// A human-readable description of the error. /// is null. internal RunObjectLastError(RunObjectLastErrorCode code, string message) @@ -54,7 +54,7 @@ internal RunObjectLastError(RunObjectLastErrorCode code, string message) } /// Initializes a new instance of . - /// One of `server_error` or `rate_limit_exceeded`. + /// One of `server_error`, `rate_limit_exceeded`, or `invalid_prompt`. /// A human-readable description of the error. /// Keeps track of any properties unknown to the library. internal RunObjectLastError(RunObjectLastErrorCode code, string message, IDictionary serializedAdditionalRawData) @@ -69,7 +69,7 @@ internal RunObjectLastError() { } - /// One of `server_error` or `rate_limit_exceeded`. + /// One of `server_error`, `rate_limit_exceeded`, or `invalid_prompt`. public RunObjectLastErrorCode Code { get; } /// A human-readable description of the error. public string Message { get; } diff --git a/.dotnet/src/Generated/Models/RunObjectLastErrorCode.cs b/.dotnet/src/Generated/Models/RunObjectLastErrorCode.cs index 500d3a25a..571959f8e 100644 --- a/.dotnet/src/Generated/Models/RunObjectLastErrorCode.cs +++ b/.dotnet/src/Generated/Models/RunObjectLastErrorCode.cs @@ -19,11 +19,14 @@ public RunObjectLastErrorCode(string value) private const string ServerErrorValue = "server_error"; private const string RateLimitExceededValue = "rate_limit_exceeded"; + private const string InvalidPromptValue = "invalid_prompt"; /// server_error. public static RunObjectLastErrorCode ServerError { get; } = new RunObjectLastErrorCode(ServerErrorValue); /// rate_limit_exceeded. public static RunObjectLastErrorCode RateLimitExceeded { get; } = new RunObjectLastErrorCode(RateLimitExceededValue); + /// invalid_prompt. + public static RunObjectLastErrorCode InvalidPrompt { get; } = new RunObjectLastErrorCode(InvalidPromptValue); /// Determines if two values are the same. public static bool operator ==(RunObjectLastErrorCode left, RunObjectLastErrorCode right) => left.Equals(right); /// Determines if two values are not the same. diff --git a/.dotnet/src/Generated/Models/AudioSegment.Serialization.cs b/.dotnet/src/Generated/Models/TranscriptionSegment.Serialization.cs similarity index 79% rename from .dotnet/src/Generated/Models/AudioSegment.Serialization.cs rename to .dotnet/src/Generated/Models/TranscriptionSegment.Serialization.cs index a677ceb5d..66d20371d 100644 --- a/.dotnet/src/Generated/Models/AudioSegment.Serialization.cs +++ b/.dotnet/src/Generated/Models/TranscriptionSegment.Serialization.cs @@ -10,14 +10,14 @@ namespace OpenAI.Internal.Models { - internal partial class AudioSegment : IJsonModel + internal partial class TranscriptionSegment : IJsonModel { - void IJsonModel.Write(Utf8JsonWriter writer, ModelReaderWriterOptions options) + void IJsonModel.Write(Utf8JsonWriter writer, ModelReaderWriterOptions options) { - var format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; + var format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; if (format != "J") { - throw new FormatException($"The model {nameof(AudioSegment)} does not support '{format}' format."); + throw new FormatException($"The model {nameof(TranscriptionSegment)} does not support '{format}' format."); } writer.WriteStartObject(); @@ -64,19 +64,19 @@ void IJsonModel.Write(Utf8JsonWriter writer, ModelReaderWriterOpti writer.WriteEndObject(); } - AudioSegment IJsonModel.Create(ref Utf8JsonReader reader, ModelReaderWriterOptions options) + TranscriptionSegment IJsonModel.Create(ref Utf8JsonReader reader, ModelReaderWriterOptions options) { - var format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; + var format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; if (format != "J") { - throw new FormatException($"The model {nameof(AudioSegment)} does not support '{format}' format."); + throw new FormatException($"The model {nameof(TranscriptionSegment)} does not support '{format}' format."); } using JsonDocument document = JsonDocument.ParseValue(ref reader); - return DeserializeAudioSegment(document.RootElement, options); + return DeserializeTranscriptionSegment(document.RootElement, options); } - internal static AudioSegment DeserializeAudioSegment(JsonElement element, ModelReaderWriterOptions options = null) + internal static TranscriptionSegment DeserializeTranscriptionSegment(JsonElement element, ModelReaderWriterOptions options = null) { options ??= new ModelReaderWriterOptions("W"); @@ -159,7 +159,7 @@ internal static AudioSegment DeserializeAudioSegment(JsonElement element, ModelR } } serializedAdditionalRawData = additionalPropertiesDictionary; - return new AudioSegment( + return new TranscriptionSegment( id, seek, start, @@ -173,43 +173,43 @@ internal static AudioSegment DeserializeAudioSegment(JsonElement element, ModelR serializedAdditionalRawData); } - BinaryData IPersistableModel.Write(ModelReaderWriterOptions options) + BinaryData IPersistableModel.Write(ModelReaderWriterOptions options) { - var format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; + var format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; switch (format) { case "J": return ModelReaderWriter.Write(this, options); default: - throw new FormatException($"The model {nameof(AudioSegment)} does not support '{options.Format}' format."); + throw new FormatException($"The model {nameof(TranscriptionSegment)} does not support '{options.Format}' format."); } } - AudioSegment IPersistableModel.Create(BinaryData data, ModelReaderWriterOptions options) + TranscriptionSegment IPersistableModel.Create(BinaryData data, ModelReaderWriterOptions options) { - var format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; + var format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; switch (format) { case "J": { using JsonDocument document = JsonDocument.Parse(data); - return DeserializeAudioSegment(document.RootElement, options); + return DeserializeTranscriptionSegment(document.RootElement, options); } default: - throw new FormatException($"The model {nameof(AudioSegment)} does not support '{options.Format}' format."); + throw new FormatException($"The model {nameof(TranscriptionSegment)} does not support '{options.Format}' format."); } } - string IPersistableModel.GetFormatFromOptions(ModelReaderWriterOptions options) => "J"; + string IPersistableModel.GetFormatFromOptions(ModelReaderWriterOptions options) => "J"; /// Deserializes the model from a raw response. /// The result to deserialize the model from. - internal static AudioSegment FromResponse(PipelineResponse response) + internal static TranscriptionSegment FromResponse(PipelineResponse response) { using var document = JsonDocument.Parse(response.Content); - return DeserializeAudioSegment(document.RootElement); + return DeserializeTranscriptionSegment(document.RootElement); } /// Convert into a Utf8JsonRequestBody. diff --git a/.dotnet/src/Generated/Models/TranscriptionSegment.cs b/.dotnet/src/Generated/Models/TranscriptionSegment.cs new file mode 100644 index 000000000..bbfd73d79 --- /dev/null +++ b/.dotnet/src/Generated/Models/TranscriptionSegment.cs @@ -0,0 +1,145 @@ +// + +using System; +using System.Collections.Generic; +using System.Linq; +using OpenAI; + +namespace OpenAI.Internal.Models +{ + /// The TranscriptionSegment. + internal partial class TranscriptionSegment + { + /// + /// Keeps track of any properties unknown to the library. + /// + /// To assign an object to the value of this property use . + /// + /// + /// To assign an already formatted json string to this property use . + /// + /// + /// Examples: + /// + /// + /// BinaryData.FromObjectAsJson("foo") + /// Creates a payload of "foo". + /// + /// + /// BinaryData.FromString("\"foo\"") + /// Creates a payload of "foo". + /// + /// + /// BinaryData.FromObjectAsJson(new { key = "value" }) + /// Creates a payload of { "key": "value" }. + /// + /// + /// BinaryData.FromString("{\"key\": \"value\"}") + /// Creates a payload of { "key": "value" }. + /// + /// + /// + /// + private IDictionary _serializedAdditionalRawData; + + /// Initializes a new instance of . + /// Unique identifier of the segment. + /// Seek offset of the segment. + /// Start time of the segment in seconds. + /// End time of the segment in seconds. + /// Text content of the segment. + /// Array of token IDs for the text content. + /// Temperature parameter used for generating the segment. + /// Average logprob of the segment. If the value is lower than -1, consider the logprobs failed. + /// + /// Compression ratio of the segment. If the value is greater than 2.4, consider the compression + /// failed. + /// + /// + /// Probability of no speech in the segment. If the value is higher than 1.0 and the `avg_logprob` + /// is below -1, consider this segment silent. + /// + /// or is null. + internal TranscriptionSegment(long id, long seek, TimeSpan start, TimeSpan end, string text, IEnumerable tokens, double temperature, double avgLogprob, double compressionRatio, double noSpeechProb) + { + Argument.AssertNotNull(text, nameof(text)); + Argument.AssertNotNull(tokens, nameof(tokens)); + + Id = id; + Seek = seek; + Start = start; + End = end; + Text = text; + Tokens = tokens.ToList(); + Temperature = temperature; + AvgLogprob = avgLogprob; + CompressionRatio = compressionRatio; + NoSpeechProb = noSpeechProb; + } + + /// Initializes a new instance of . + /// Unique identifier of the segment. + /// Seek offset of the segment. + /// Start time of the segment in seconds. + /// End time of the segment in seconds. + /// Text content of the segment. + /// Array of token IDs for the text content. + /// Temperature parameter used for generating the segment. + /// Average logprob of the segment. If the value is lower than -1, consider the logprobs failed. + /// + /// Compression ratio of the segment. If the value is greater than 2.4, consider the compression + /// failed. + /// + /// + /// Probability of no speech in the segment. If the value is higher than 1.0 and the `avg_logprob` + /// is below -1, consider this segment silent. + /// + /// Keeps track of any properties unknown to the library. + internal TranscriptionSegment(long id, long seek, TimeSpan start, TimeSpan end, string text, IReadOnlyList tokens, double temperature, double avgLogprob, double compressionRatio, double noSpeechProb, IDictionary serializedAdditionalRawData) + { + Id = id; + Seek = seek; + Start = start; + End = end; + Text = text; + Tokens = tokens; + Temperature = temperature; + AvgLogprob = avgLogprob; + CompressionRatio = compressionRatio; + NoSpeechProb = noSpeechProb; + _serializedAdditionalRawData = serializedAdditionalRawData; + } + + /// Initializes a new instance of for deserialization. + internal TranscriptionSegment() + { + } + + /// Unique identifier of the segment. + public long Id { get; } + /// Seek offset of the segment. + public long Seek { get; } + /// Start time of the segment in seconds. + public TimeSpan Start { get; } + /// End time of the segment in seconds. + public TimeSpan End { get; } + /// Text content of the segment. + public string Text { get; } + /// Array of token IDs for the text content. + public IReadOnlyList Tokens { get; } + /// Temperature parameter used for generating the segment. + public double Temperature { get; } + /// Average logprob of the segment. If the value is lower than -1, consider the logprobs failed. + public double AvgLogprob { get; } + /// + /// Compression ratio of the segment. If the value is greater than 2.4, consider the compression + /// failed. + /// + public double CompressionRatio { get; } + /// + /// Probability of no speech in the segment. If the value is higher than 1.0 and the `avg_logprob` + /// is below -1, consider this segment silent. + /// + public double NoSpeechProb { get; } + } +} diff --git a/.dotnet/src/Generated/Models/TranscriptionWord.Serialization.cs b/.dotnet/src/Generated/Models/TranscriptionWord.Serialization.cs new file mode 100644 index 000000000..0292f78cf --- /dev/null +++ b/.dotnet/src/Generated/Models/TranscriptionWord.Serialization.cs @@ -0,0 +1,146 @@ +// + +using System; +using OpenAI.ClientShared.Internal; +using System.ClientModel; +using System.ClientModel.Primitives; +using System.Collections.Generic; +using System.Text.Json; +using OpenAI; + +namespace OpenAI.Internal.Models +{ + internal partial class TranscriptionWord : IJsonModel + { + void IJsonModel.Write(Utf8JsonWriter writer, ModelReaderWriterOptions options) + { + var format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; + if (format != "J") + { + throw new FormatException($"The model {nameof(TranscriptionWord)} does not support '{format}' format."); + } + + writer.WriteStartObject(); + writer.WritePropertyName("word"u8); + writer.WriteStringValue(Word); + writer.WritePropertyName("start"u8); + writer.WriteNumberValue(Convert.ToInt32(Start.ToString("%s"))); + writer.WritePropertyName("end"u8); + writer.WriteNumberValue(Convert.ToInt32(End.ToString("%s"))); + if (options.Format != "W" && _serializedAdditionalRawData != null) + { + foreach (var item in _serializedAdditionalRawData) + { + writer.WritePropertyName(item.Key); +#if NET6_0_OR_GREATER + writer.WriteRawValue(item.Value); +#else + using (JsonDocument document = JsonDocument.Parse(item.Value)) + { + JsonSerializer.Serialize(writer, document.RootElement); + } +#endif + } + } + writer.WriteEndObject(); + } + + TranscriptionWord IJsonModel.Create(ref Utf8JsonReader reader, ModelReaderWriterOptions options) + { + var format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; + if (format != "J") + { + throw new FormatException($"The model {nameof(TranscriptionWord)} does not support '{format}' format."); + } + + using JsonDocument document = JsonDocument.ParseValue(ref reader); + return DeserializeTranscriptionWord(document.RootElement, options); + } + + internal static TranscriptionWord DeserializeTranscriptionWord(JsonElement element, ModelReaderWriterOptions options = null) + { + options ??= new ModelReaderWriterOptions("W"); + + if (element.ValueKind == JsonValueKind.Null) + { + return null; + } + string word = default; + TimeSpan start = default; + TimeSpan end = default; + IDictionary serializedAdditionalRawData = default; + Dictionary additionalPropertiesDictionary = new Dictionary(); + foreach (var property in element.EnumerateObject()) + { + if (property.NameEquals("word"u8)) + { + word = property.Value.GetString(); + continue; + } + if (property.NameEquals("start"u8)) + { + start = TimeSpan.FromSeconds(property.Value.GetInt32()); + continue; + } + if (property.NameEquals("end"u8)) + { + end = TimeSpan.FromSeconds(property.Value.GetInt32()); + continue; + } + if (options.Format != "W") + { + additionalPropertiesDictionary.Add(property.Name, BinaryData.FromString(property.Value.GetRawText())); + } + } + serializedAdditionalRawData = additionalPropertiesDictionary; + return new TranscriptionWord(word, start, end, serializedAdditionalRawData); + } + + BinaryData IPersistableModel.Write(ModelReaderWriterOptions options) + { + var format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; + + switch (format) + { + case "J": + return ModelReaderWriter.Write(this, options); + default: + throw new FormatException($"The model {nameof(TranscriptionWord)} does not support '{options.Format}' format."); + } + } + + TranscriptionWord IPersistableModel.Create(BinaryData data, ModelReaderWriterOptions options) + { + var format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; + + switch (format) + { + case "J": + { + using JsonDocument document = JsonDocument.Parse(data); + return DeserializeTranscriptionWord(document.RootElement, options); + } + default: + throw new FormatException($"The model {nameof(TranscriptionWord)} does not support '{options.Format}' format."); + } + } + + string IPersistableModel.GetFormatFromOptions(ModelReaderWriterOptions options) => "J"; + + /// Deserializes the model from a raw response. + /// The result to deserialize the model from. + internal static TranscriptionWord FromResponse(PipelineResponse response) + { + using var document = JsonDocument.Parse(response.Content); + return DeserializeTranscriptionWord(document.RootElement); + } + + /// Convert into a Utf8JsonRequestBody. + internal virtual BinaryContent ToRequestBody() + { + var content = new Utf8JsonRequestBody(); + content.JsonWriter.WriteObjectValue(this); + return content; + } + } +} diff --git a/.dotnet/src/Generated/Models/TranscriptionWord.cs b/.dotnet/src/Generated/Models/TranscriptionWord.cs new file mode 100644 index 000000000..b3bd0260c --- /dev/null +++ b/.dotnet/src/Generated/Models/TranscriptionWord.cs @@ -0,0 +1,83 @@ +// + +using System; +using System.Collections.Generic; +using OpenAI; + +namespace OpenAI.Internal.Models +{ + /// The TranscriptionWord. + internal partial class TranscriptionWord + { + /// + /// Keeps track of any properties unknown to the library. + /// + /// To assign an object to the value of this property use . + /// + /// + /// To assign an already formatted json string to this property use . + /// + /// + /// Examples: + /// + /// + /// BinaryData.FromObjectAsJson("foo") + /// Creates a payload of "foo". + /// + /// + /// BinaryData.FromString("\"foo\"") + /// Creates a payload of "foo". + /// + /// + /// BinaryData.FromObjectAsJson(new { key = "value" }) + /// Creates a payload of { "key": "value" }. + /// + /// + /// BinaryData.FromString("{\"key\": \"value\"}") + /// Creates a payload of { "key": "value" }. + /// + /// + /// + /// + private IDictionary _serializedAdditionalRawData; + + /// Initializes a new instance of . + /// The text content of the word. + /// Start time of the word in seconds. + /// End time of the word in seconds. + /// is null. + internal TranscriptionWord(string word, TimeSpan start, TimeSpan end) + { + Argument.AssertNotNull(word, nameof(word)); + + Word = word; + Start = start; + End = end; + } + + /// Initializes a new instance of . + /// The text content of the word. + /// Start time of the word in seconds. + /// End time of the word in seconds. + /// Keeps track of any properties unknown to the library. + internal TranscriptionWord(string word, TimeSpan start, TimeSpan end, IDictionary serializedAdditionalRawData) + { + Word = word; + Start = start; + End = end; + _serializedAdditionalRawData = serializedAdditionalRawData; + } + + /// Initializes a new instance of for deserialization. + internal TranscriptionWord() + { + } + + /// The text content of the word. + public string Word { get; } + /// Start time of the word in seconds. + public TimeSpan Start { get; } + /// End time of the word in seconds. + public TimeSpan End { get; } + } +} diff --git a/.dotnet/src/Generated/Moderations.cs b/.dotnet/src/Generated/Moderations.cs index 0e76f8341..13710e458 100644 --- a/.dotnet/src/Generated/Moderations.cs +++ b/.dotnet/src/Generated/Moderations.cs @@ -39,7 +39,7 @@ internal Moderations(ClientPipeline pipeline, ApiKeyCredential credential, Uri e _endpoint = endpoint; } - /// Classifies if text violates OpenAI's Content Policy. + /// Classifies if text is potentially harmful. /// The to use. /// is null. public virtual async Task> CreateModerationAsync(CreateModerationRequest content) @@ -51,7 +51,7 @@ public virtual async Task> CreateModerati return ClientResult.FromValue(CreateModerationResponse.FromResponse(result.GetRawResponse()), result.GetRawResponse()); } - /// Classifies if text violates OpenAI's Content Policy. + /// Classifies if text is potentially harmful. /// The to use. /// is null. public virtual ClientResult CreateModeration(CreateModerationRequest content) @@ -64,7 +64,7 @@ public virtual ClientResult CreateModeration(CreateMod } /// - /// [Protocol Method] Classifies if text violates OpenAI's Content Policy + /// [Protocol Method] Classifies if text is potentially harmful. /// /// /// @@ -103,7 +103,7 @@ public virtual async Task CreateModerationAsync(BinaryContent cont } /// - /// [Protocol Method] Classifies if text violates OpenAI's Content Policy + /// [Protocol Method] Classifies if text is potentially harmful. /// /// /// diff --git a/.dotnet/src/Generated/OpenAIModelFactory.cs b/.dotnet/src/Generated/OpenAIModelFactory.cs index 0c7bcab01..749081bca 100644 --- a/.dotnet/src/Generated/OpenAIModelFactory.cs +++ b/.dotnet/src/Generated/OpenAIModelFactory.cs @@ -706,7 +706,7 @@ public static RunToolCallObjectFunction RunToolCallObjectFunction(string name = } /// Initializes a new instance of . - /// One of `server_error` or `rate_limit_exceeded`. + /// One of `server_error`, `rate_limit_exceeded`, or `invalid_prompt`. /// A human-readable description of the error. /// A new instance for mocking. public static RunObjectLastError RunObjectLastError(RunObjectLastErrorCode code = default, string message = null) diff --git a/.dotnet/src/OpenAI.csproj b/.dotnet/src/OpenAI.csproj index ed91914a5..d349c121a 100644 --- a/.dotnet/src/OpenAI.csproj +++ b/.dotnet/src/OpenAI.csproj @@ -10,7 +10,7 @@ - + diff --git a/.dotnet/src/Polyfill/System.Diagnostics.CodeAnalysis.ExperimentalAttribute.cs b/.dotnet/src/Polyfill/System.Diagnostics.CodeAnalysis.ExperimentalAttribute.cs new file mode 100644 index 000000000..163d98482 --- /dev/null +++ b/.dotnet/src/Polyfill/System.Diagnostics.CodeAnalysis.ExperimentalAttribute.cs @@ -0,0 +1,59 @@ +#if !NET8_0_OR_GREATER + +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.Diagnostics.CodeAnalysis +{ + /// + /// Indicates that an API is experimental and it may change in the future. + /// + /// + /// This attribute allows call sites to be flagged with a diagnostic that indicates that an experimental + /// feature is used. Authors can use this attribute to ship preview features in their assemblies. + /// + [AttributeUsage(AttributeTargets.Assembly | + AttributeTargets.Module | + AttributeTargets.Class | + AttributeTargets.Struct | + AttributeTargets.Enum | + AttributeTargets.Constructor | + AttributeTargets.Method | + AttributeTargets.Property | + AttributeTargets.Field | + AttributeTargets.Event | + AttributeTargets.Interface | + AttributeTargets.Delegate, Inherited = false)] + internal sealed class ExperimentalAttribute : Attribute + { + /// + /// Initializes a new instance of the class, specifying the ID that the compiler will use + /// when reporting a use of the API the attribute applies to. + /// + /// The ID that the compiler will use when reporting a use of the API the attribute applies to. + public ExperimentalAttribute(string diagnosticId) + { + DiagnosticId = diagnosticId; + } + + /// + /// Gets the ID that the compiler will use when reporting a use of the API the attribute applies to. + /// + /// The unique diagnostic ID. + /// + /// The diagnostic ID is shown in build output for warnings and errors. + /// This property represents the unique ID that can be used to suppress the warnings or errors, if needed. + /// + public string DiagnosticId { get; } + + /// + /// Gets or sets the URL for corresponding documentation. + /// The API accepts a format string instead of an actual URL, creating a generic URL that includes the diagnostic ID. + /// + /// The format string that represents a URL to corresponding documentation. + /// An example format string is https://contoso.com/obsoletion-warnings/{0}. + public string? UrlFormat { get; set; } + } +} + +#endif // !NET8_0_OR_LATER \ No newline at end of file diff --git a/.dotnet/src/Utility/MultipartFormDataBinaryContent.cs b/.dotnet/src/Utility/MultipartFormDataBinaryContent.cs new file mode 100644 index 000000000..adce9e4f1 --- /dev/null +++ b/.dotnet/src/Utility/MultipartFormDataBinaryContent.cs @@ -0,0 +1,155 @@ +using System; +using System.ClientModel; +using System.Diagnostics; +using System.Globalization; +using System.IO; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Threading; +using System.Threading.Tasks; + +namespace OpenAI.Internal; + +internal class MultipartFormDataBinaryContent : BinaryContent +{ + private readonly MultipartFormDataContent _multipartContent; + + private static Random _random = new(); + private static readonly char[] _boundaryValues = "0123456789=ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz".ToCharArray(); + + public MultipartFormDataBinaryContent() + { + _multipartContent = new MultipartFormDataContent(CreateBoundary()); + } + + public string ContentType + { + get + { + Debug.Assert(_multipartContent.Headers.ContentType is not null); + + return _multipartContent.Headers.ContentType!.ToString(); + } + } + + internal HttpContent HttpContent => _multipartContent; + + public void Add(Stream stream, string name, string fileName = default) + { + Add(new StreamContent(stream), name, fileName); + } + + public void Add(string content, string name, string fileName = default) + { + Add(new StringContent(content), name, fileName); + } + + public void Add(int content, string name, string fileName = default) + { + // https://learn.microsoft.com/en-us/dotnet/standard/base-types/standard-numeric-format-strings#GFormatString + string value = content.ToString("G", CultureInfo.InvariantCulture); + Add(new StringContent(value), name, fileName); + } + + public void Add(double content, string name, string fileName = default) + { + // https://learn.microsoft.com/en-us/dotnet/standard/base-types/standard-numeric-format-strings#GFormatString + string value = content.ToString("G", CultureInfo.InvariantCulture); + Add(new StringContent(value), name, fileName); + } + + public void Add(byte[] content, string name, string fileName = default) + { + Add(new ByteArrayContent(content), name, fileName); + } + + public void Add(BinaryData content, string name, string fileName = default) + { + Add(new ByteArrayContent(content.ToArray()), name, fileName); + } + + private void Add(HttpContent content, string name, string fileName) + { + if (fileName is not null) + { + AddFileNameHeader(content, name, fileName); + } + + _multipartContent.Add(content, name); + } + + private static void AddFileNameHeader(HttpContent content, string name, string filename) + { + // Add the content header manually because the default implementation + // adds a `filename*` parameter to the header, which RFC 7578 says not + // to do. We are following up with the BCL team per correctness. + ContentDispositionHeaderValue header = new("form-data") + { + Name = name, + FileName = filename + }; + content.Headers.ContentDisposition = header; + } + + private static string CreateBoundary() + { + Span chars = new char[70]; + + byte[] random = new byte[70]; + _random.NextBytes(random); + + // The following will sample evenly from the possible values. + // This is important to ensuring that the odds of creating a boundary + // that occurs in any content part are astronomically small. + int mask = 255 >> 2; + + Debug.Assert(_boundaryValues.Length - 1 == mask); + + for (int i = 0; i < 70; i++) + { + chars[i] = _boundaryValues[random[i] & mask]; + } + + return chars.ToString(); + } + + public override bool TryComputeLength(out long length) + { + // We can't call the protected method on HttpContent + + if (_multipartContent.Headers.ContentLength is long contentLength) + { + length = contentLength; + return true; + } + + length = 0; + return false; + } + + public override void WriteTo(Stream stream, CancellationToken cancellationToken = default) + { + // TODO: polyfill sync-over-async for netstandard2.0 for Azure clients. + // Tracked by https://github.com/Azure/azure-sdk-for-net/issues/42674 + +#if NET6_0_OR_GREATER + _multipartContent.CopyTo(stream, default, cancellationToken); +#else + _multipartContent.CopyToAsync(stream).GetAwaiter().GetResult(); +#endif + } + + public override async Task WriteToAsync(Stream stream, CancellationToken cancellationToken = default) + { +#if NET6_0_OR_GREATER + await _multipartContent.CopyToAsync(stream, cancellationToken).ConfigureAwait(false); +#else + await _multipartContent.CopyToAsync(stream).ConfigureAwait(false); +#endif + } + + public override void Dispose() + { + _multipartContent.Dispose(); + } +} diff --git a/.dotnet/src/Utility/SseReader.cs b/.dotnet/src/Utility/SseReader.cs index cab725caf..fc2da5339 100644 --- a/.dotnet/src/Utility/SseReader.cs +++ b/.dotnet/src/Utility/SseReader.cs @@ -26,12 +26,14 @@ public SseReader(Stream stream) /// /// The next in the stream, or null once no more data can be read from the stream. /// + // TODO: Can this be an IEnumerable instead of using retur-null semantics? public ServerSentEvent? TryGetNextEvent(CancellationToken cancellationToken = default) { List fields = []; while (!cancellationToken.IsCancellationRequested) { + // TODO: can this be UTF-8 all the way down? string line = _reader.ReadLine(); if (line == null) { diff --git a/.dotnet/src/Utility/StreamingClientResultOfT.cs b/.dotnet/src/Utility/StreamingClientResultOfT.cs new file mode 100644 index 000000000..8fbece850 --- /dev/null +++ b/.dotnet/src/Utility/StreamingClientResultOfT.cs @@ -0,0 +1,27 @@ +using System.ClientModel; +using System.ClientModel.Primitives; +using System.Collections.Generic; +using System.Threading; + +namespace OpenAI; + +#pragma warning disable CS1591 // public XML comments + +/// +/// Represents an operation response with streaming content that can be deserialized and enumerated while the response +/// is still being received. +/// +/// The data type representative of distinct, streamable items. +public abstract class StreamingClientResult : ClientResult, IAsyncEnumerable +{ + protected StreamingClientResult(PipelineResponse response) : base(response) + { + } + + // Note that if the implementation disposes the stream, the caller can only + // enumerate the results once. I think this makes sense, but we should + // make sure architects agree. + public abstract IAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken = default); +} + +#pragma warning restore CS1591 // public XML comments \ No newline at end of file diff --git a/.dotnet/src/Utility/StreamingResult.cs b/.dotnet/src/Utility/StreamingResult.cs deleted file mode 100644 index a1b6ff538..000000000 --- a/.dotnet/src/Utility/StreamingResult.cs +++ /dev/null @@ -1,95 +0,0 @@ -using System.ClientModel; -using System.ClientModel; -using System.ClientModel.Primitives; -using System.Threading; -using System.Collections.Generic; -using System; - -namespace OpenAI; - -/// -/// Represents an operation response with streaming content that can be deserialized and enumerated while the response -/// is still being received. -/// -/// The data type representative of distinct, streamable items. -public class StreamingClientResult - : IDisposable - , IAsyncEnumerable -{ - private ClientResult _rawResult { get; } - private IAsyncEnumerable _asyncEnumerableSource { get; } - private bool _disposedValue { get; set; } - - private StreamingClientResult() { } - - private StreamingClientResult( - ClientResult rawResult, - Func> asyncEnumerableProcessor) - { - _rawResult = rawResult; - _asyncEnumerableSource = asyncEnumerableProcessor.Invoke(rawResult); - } - - /// - /// Creates a new instance of using the provided underlying HTTP response. The - /// provided function will be used to resolve the response into an asynchronous enumeration of streamed response - /// items. - /// - /// The HTTP response. - /// - /// The function that will resolve the provided response into an IAsyncEnumerable. - /// - /// - /// A new instance of that will be capable of asynchronous enumeration of - /// items from the HTTP response. - /// - internal static StreamingClientResult CreateFromResponse( - ClientResult result, - Func> asyncEnumerableProcessor) - { - return new(result, asyncEnumerableProcessor); - } - - /// - /// Gets the underlying instance that this may enumerate - /// over. - /// - /// The instance attached to this . - public PipelineResponse GetRawResponse() => _rawResult.GetRawResponse(); - - /// - /// Gets the asynchronously enumerable collection of distinct, streamable items in the response. - /// - /// - /// The return value of this method may be used with the "await foreach" statement. - /// - /// As explicitly implements , callers may - /// enumerate a instance directly instead of calling this method. - /// - /// - /// - public IAsyncEnumerable EnumerateValues() => this; - - /// - public void Dispose() - { - Dispose(disposing: true); - GC.SuppressFinalize(this); - } - - /// - protected virtual void Dispose(bool disposing) - { - if (!_disposedValue) - { - if (disposing) - { - _rawResult?.GetRawResponse()?.Dispose(); - } - _disposedValue = true; - } - } - - IAsyncEnumerator IAsyncEnumerable.GetAsyncEnumerator(CancellationToken cancellationToken) - => _asyncEnumerableSource.GetAsyncEnumerator(cancellationToken); -} \ No newline at end of file diff --git a/.dotnet/src/Utility/System.ClientModel.MultipartContent.cs b/.dotnet/src/Utility/System.ClientModel.MultipartContent.cs deleted file mode 100644 index 6342d7552..000000000 --- a/.dotnet/src/Utility/System.ClientModel.MultipartContent.cs +++ /dev/null @@ -1,367 +0,0 @@ - - -using System.ClientModel.Primitives; -using System.Collections.Generic; -using System.IO; -using System.Text; -using System.Threading; -using System.Threading.Tasks; - -namespace System.ClientModel; - -// Placeholder implementation adapted from: -// https://github.com/Azure/azure-sdk-for-net/blob/main/sdk/core/Azure.Core/src/Shared/Multipart/MultipartContent.cs - -internal partial class MultipartContent : BinaryContent -{ - #region Fields - - private const string CrLf = "\r\n"; - private const string ColonSP = ": "; - - private static readonly int s_crlfLength = GetEncodedLength(CrLf); - private static readonly int s_dashDashLength = GetEncodedLength("--"); - private static readonly int s_colonSpaceLength = GetEncodedLength(ColonSP); - - private readonly List _nestedContent; - private readonly string _subtype; - private readonly string _boundary; - internal readonly Dictionary _headers; - - #endregion Fields - - #region Construction - - public MultipartContent() - : this("mixed", GetDefaultBoundary()) - { } - - public MultipartContent(string subtype) - : this(subtype, GetDefaultBoundary()) - { } - - /// - /// Initializes a new instance of the class. - /// - /// The multipart sub type. - /// The boundary string for the multipart form data content. - public MultipartContent(string subtype, string boundary) - { - ValidateBoundary(boundary); - _subtype = subtype; - - // see https://www.ietf.org/rfc/rfc1521.txt page 29. - _boundary = boundary.Contains(":") ? $"\"{boundary}\"" : boundary; - _headers = new Dictionary - { - ["content-type"] = $"multipart/{_subtype}; boundary={_boundary}" - }; - - _nestedContent = new List(); - } - - private static void ValidateBoundary(string boundary) - { - // NameValueHeaderValue is too restrictive for boundary. - // Instead validate it ourselves and then quote it. - if (string.IsNullOrWhiteSpace(boundary)) throw new ArgumentException(nameof(boundary)); - - // cspell:disable - // RFC 2046 Section 5.1.1 - // boundary := 0*69 bcharsnospace - // bchars := bcharsnospace / " " - // bcharsnospace := DIGIT / ALPHA / "'" / "(" / ")" / "+" / "_" / "," / "-" / "." / "/" / ":" / "=" / "?" - // cspell:enable - if (boundary.Length > 70) - { - throw new ArgumentOutOfRangeException(nameof(boundary), boundary, $"The field cannot be longer than {70} characters."); - } - // Cannot end with space. - if (boundary.EndsWith(" ", StringComparison.InvariantCultureIgnoreCase)) - { - throw new ArgumentException($"The format of value '{boundary}' is invalid.", nameof(boundary)); - } - - const string AllowedMarks = @"'()+_,-./:=? "; - - foreach (char ch in boundary) - { - if (('0' <= ch && ch <= '9') || // Digit. - ('a' <= ch && ch <= 'z') || // alpha. - ('A' <= ch && ch <= 'Z') || // ALPHA. - AllowedMarks.Contains(char.ToString(ch))) // Marks. - { - // Valid. - } - else - { - throw new ArgumentException($"The format of value '{boundary}' is invalid.", nameof(boundary)); - } - } - } - - private static string GetDefaultBoundary() - { - return Guid.NewGuid().ToString(); - } - - /// - /// Add content type header to the request. - /// - /// The request. - public void ApplyToRequest(PipelineRequest request) - { - request.Headers.Set("content-type", $"multipart/{_subtype}; boundary={_boundary}"); - request.Content = this; - } - - /// - /// Add HTTP content to a collection of RequestContent objects that - /// get serialized to multipart/form-data MIME type. - /// - /// The Request content to add to the collection. - public virtual void Add(BinaryContent content) - { - if (content is null) throw new ArgumentNullException(nameof(content)); - AddInternal(content, null); - } - - /// - /// Add HTTP content to a collection of RequestContent objects that - /// get serialized to multipart/form-data MIME type. - /// - /// The Request content to add to the collection. - /// The headers to add to the collection. - public virtual void Add(BinaryContent content, Dictionary headers) - { - if (content is null) throw new ArgumentNullException(nameof(content)); - if (headers is null) throw new ArgumentNullException(nameof(headers)); - - AddInternal(content, headers); - } - - private void AddInternal(BinaryContent content, Dictionary headers) - { - headers ??= []; - _nestedContent.Add(new MultipartRequestContent(content, headers)); - } - - #endregion Construction - - #region Dispose - - /// - /// Frees resources held by the object. - /// - public override void Dispose() - { - foreach (MultipartRequestContent content in _nestedContent) - { - content.RequestContent.Dispose(); - } - _nestedContent.Clear(); - } - - #endregion Dispose - - #region Serialization - - // for-each content - // write "--" + boundary - // for-each content header - // write header: header-value - // write content.WriteTo[Async] - // write "--" + boundary + "--" - // Can't be canceled directly by the user. If the overall request is canceled - // then the stream will be closed an exception thrown. - /// - /// - /// - /// - /// - /// - public override void WriteTo(Stream stream, CancellationToken cancellationToken) - { - if (stream is null) throw new ArgumentNullException(nameof(stream)); - - try - { - // Write start boundary. - EncodeStringToStream(stream, "--" + _boundary + CrLf); - - // Write each nested content. - var output = new StringBuilder(); - for (int contentIndex = 0; contentIndex < _nestedContent.Count; contentIndex++) - { - // Write divider, headers, and content. - BinaryContent content = _nestedContent[contentIndex].RequestContent; - Dictionary headers = _nestedContent[contentIndex].Headers; - EncodeStringToStream(stream, SerializeHeadersToString(output, contentIndex, headers)); - content.WriteTo(stream, cancellationToken); - } - - // Write footer boundary. - EncodeStringToStream(stream, CrLf + "--" + _boundary + "--" + CrLf); - } - catch (Exception) - { - throw; - } - } - - // for-each content - // write "--" + boundary - // for-each content header - // write header: header-value - // write content.WriteTo[Async] - // write "--" + boundary + "--" - // Can't be canceled directly by the user. If the overall request is canceled - // then the stream will be closed an exception thrown. - /// - /// - /// - /// - /// - /// - public override Task WriteToAsync(Stream stream, CancellationToken cancellation) => - SerializeToStreamAsync(stream, cancellation); - - private async Task SerializeToStreamAsync(Stream stream, CancellationToken cancellationToken) - { - if (stream is null) throw new ArgumentNullException(nameof(stream)); - try - { - // Write start boundary. - await EncodeStringToStreamAsync(stream, "--" + _boundary + CrLf, cancellationToken).ConfigureAwait(false); - - // Write each nested content. - var output = new StringBuilder(); - for (int contentIndex = 0; contentIndex < _nestedContent.Count; contentIndex++) - { - // Write divider, headers, and content. - BinaryContent content = _nestedContent[contentIndex].RequestContent; - Dictionary headers = _nestedContent[contentIndex].Headers; - await EncodeStringToStreamAsync(stream, SerializeHeadersToString(output, contentIndex, headers), cancellationToken).ConfigureAwait(false); - await content.WriteToAsync(stream, cancellationToken).ConfigureAwait(false); - } - - // Write footer boundary. - await EncodeStringToStreamAsync(stream, CrLf + "--" + _boundary + "--" + CrLf, cancellationToken).ConfigureAwait(false); - } - catch (Exception) - { - throw; - } - } - - private string SerializeHeadersToString(StringBuilder scratch, int contentIndex, Dictionary headers) - { - scratch.Clear(); - - // Add divider. - if (contentIndex != 0) // Write divider for all but the first content. - { - scratch.Append(CrLf + "--"); // const strings - scratch.Append(_boundary); - scratch.Append(CrLf); - } - - // Add headers. - foreach (KeyValuePair header in headers) - { - scratch.Append(header.Key); - scratch.Append(": "); - scratch.Append(header.Value); - scratch.Append(CrLf); - } - - // Extra CRLF to end headers (even if there are no headers). - scratch.Append(CrLf); - - return scratch.ToString(); - } - - private static void EncodeStringToStream(Stream stream, string input) - { - byte[] buffer = Encoding.Default.GetBytes(input); - stream.Write(buffer, 0, buffer.Length); - } - - private static Task EncodeStringToStreamAsync(Stream stream, string input, CancellationToken cancellationToken) - { - byte[] buffer = Encoding.Default.GetBytes(input); - return stream.WriteAsync(buffer, 0, buffer.Length, cancellationToken); - } - - /// - /// Attempts to compute the length of the underlying content, if available. - /// - /// The length of the underlying data. - public override bool TryComputeLength(out long length) - { - int boundaryLength = GetEncodedLength(_boundary); - - long currentLength = 0; - long internalBoundaryLength = s_crlfLength + s_dashDashLength + boundaryLength + s_crlfLength; - - // Start Boundary. - currentLength += s_dashDashLength + boundaryLength + s_crlfLength; - - bool first = true; - foreach (MultipartRequestContent content in _nestedContent) - { - if (first) - { - first = false; // First boundary already written. - } - else - { - // Internal Boundary. - currentLength += internalBoundaryLength; - } - - // Headers. - foreach (KeyValuePair headerPair in content.Headers) - { - currentLength += GetEncodedLength(headerPair.Key) + s_colonSpaceLength; - currentLength += GetEncodedLength(headerPair.Value); - currentLength += s_crlfLength; - } - - currentLength += s_crlfLength; - - // Content. - if (!content.RequestContent.TryComputeLength(out long tempContentLength)) - { - length = 0; - return false; - } - currentLength += tempContentLength; - } - - // Terminating boundary. - currentLength += s_crlfLength + s_dashDashLength + boundaryLength + s_dashDashLength + s_crlfLength; - - length = currentLength; - return true; - } - - private static int GetEncodedLength(string input) - { - return Encoding.Default.GetByteCount(input); - } - - #endregion Serialization - - private class MultipartRequestContent - { - public readonly BinaryContent RequestContent; - public Dictionary Headers; - - public MultipartRequestContent(BinaryContent content, Dictionary headers) - { - RequestContent = content; - Headers = headers; - } - } -} \ No newline at end of file diff --git a/.dotnet/src/Utility/System.ClientModel.MultipartFormDataContent.cs b/.dotnet/src/Utility/System.ClientModel.MultipartFormDataContent.cs deleted file mode 100644 index 9faceac1c..000000000 --- a/.dotnet/src/Utility/System.ClientModel.MultipartFormDataContent.cs +++ /dev/null @@ -1,117 +0,0 @@ -using System.Collections.Generic; - -namespace System.ClientModel; - -// Placeholder implementation adapted from: -// https://github.com/Azure/azure-sdk-for-net/blob/main/sdk/core/Azure.Core/src/Shared/Multipart/MultipartFormDataContent.cs - -internal partial class MultipartFormDataContent : MultipartContent -{ - // Copyright (c) Microsoft Corporation. All rights reserved. - // Licensed under the MIT License. - -#nullable disable - - #region Fields - - private const string FormData = "form-data"; - - #endregion Fields - - #region Construction - - /// - /// Initializes a new instance of the class. - /// - public MultipartFormDataContent() : base(FormData) - { } - - /// - /// Initializes a new instance of the class. - /// - /// The boundary string for the multipart form data content. - public MultipartFormDataContent(string boundary) : base(FormData, boundary) - { } - - #endregion Construction - - /// - /// Add HTTP content to a collection of RequestContent objects that - /// get serialized to multipart/form-data MIME type. - /// - /// The Request content to add to the collection. - public override void Add(BinaryContent content) - { - if (content is null) throw new ArgumentNullException(nameof(content)); - AddInternal(content, null, null, null); - } - - /// - /// Add HTTP content to a collection of RequestContent objects that - /// get serialized to multipart/form-data MIME type. - /// - /// The Request content to add to the collection. - /// The headers to add to the collection. - public override void Add(BinaryContent content, Dictionary headers) - { - if (content is null) throw new ArgumentNullException(nameof(content)); - if (headers is null) throw new ArgumentNullException(nameof(headers)); - - AddInternal(content, headers, null, null); - } - - /// - /// Add HTTP content to a collection of RequestContent objects that - /// get serialized to multipart/form-data MIME type. - /// - /// The Request content to add to the collection. - /// The name for the request content to add. - /// The headers to add to the collection. - public void Add(BinaryContent content, string name, Dictionary headers) - { - if (content is null) throw new ArgumentNullException(nameof(content)); - if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException(nameof(name)); - - AddInternal(content, headers, name, null); - } - - /// - /// Add HTTP content to a collection of RequestContent objects that - /// get serialized to multipart/form-data MIME type. - /// - /// The Request content to add to the collection. - /// The name for the request content to add. - /// The file name for the request content to add to the collection. - /// The headers to add to the collection. - public void Add(BinaryContent content, string name, string fileName, Dictionary headers) - { - if (content is null) throw new ArgumentNullException(nameof(content)); - if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException(nameof(name)); - if (string.IsNullOrWhiteSpace(fileName)) throw new ArgumentException(nameof(fileName)); - - AddInternal(content, headers, name, fileName); - } - - private void AddInternal(BinaryContent content, Dictionary headers, string name, string fileName) - { - headers ??= []; - - if (!headers.ContainsKey("Content-Disposition")) - { - var value = FormData; - - if (name != null) - { - value = value + "; name=" + name; - } - if (fileName != null) - { - value = value + "; filename=" + fileName; - } - - headers.Add("Content-Disposition", value); - } - - base.Add(content, headers); - } -} \ No newline at end of file diff --git a/.dotnet/tests/Assets/hola_mundo.m4a b/.dotnet/tests/Assets/hola_mundo.m4a deleted file mode 100644 index 7bc01b755..000000000 Binary files a/.dotnet/tests/Assets/hola_mundo.m4a and /dev/null differ diff --git a/.dotnet/tests/Assets/multilingual.wav b/.dotnet/tests/Assets/multilingual.wav new file mode 100644 index 000000000..847f3463a Binary files /dev/null and b/.dotnet/tests/Assets/multilingual.wav differ diff --git a/.dotnet/tests/Directory.Build.targets b/.dotnet/tests/Directory.Build.targets new file mode 100644 index 000000000..9108ca3f9 --- /dev/null +++ b/.dotnet/tests/Directory.Build.targets @@ -0,0 +1,7 @@ + + + + PreserveNewest + + + \ No newline at end of file diff --git a/.dotnet/tests/OpenAI.Tests.csproj b/.dotnet/tests/OpenAI.Tests.csproj index aaf8385b2..cf68d6d01 100644 --- a/.dotnet/tests/OpenAI.Tests.csproj +++ b/.dotnet/tests/OpenAI.Tests.csproj @@ -14,6 +14,6 @@ - + \ No newline at end of file diff --git a/.dotnet/tests/Samples/Assistants/Sample01_RetrievalAugmentedGeneration.cs b/.dotnet/tests/Samples/Assistants/Sample01_RetrievalAugmentedGeneration.cs index cc8431f19..bb67aed46 100644 --- a/.dotnet/tests/Samples/Assistants/Sample01_RetrievalAugmentedGeneration.cs +++ b/.dotnet/tests/Samples/Assistants/Sample01_RetrievalAugmentedGeneration.cs @@ -13,12 +13,14 @@ public partial class AssistantSamples [Ignore("Compilation validation only")] public void Sample01_RetrievalAugmentedGeneration() { + // Assistants is a beta API and subject to change; acknowledge its experimental status by suppressing the matching warning. +#pragma warning disable OPENAI001 OpenAIClient openAIClient = new(Environment.GetEnvironmentVariable("OpenAIClient_KEY")); FileClient fileClient = openAIClient.GetFileClient(); AssistantClient assistantClient = openAIClient.GetAssistantClient(); // First, let's contrive a document we'll use retrieval with and upload it. - BinaryData document = BinaryData.FromString(""" + using Stream document = BinaryData.FromString(""" { "description": "This document contains the sale history data for Contoso products.", "sales": [ @@ -45,7 +47,7 @@ public void Sample01_RetrievalAugmentedGeneration() } ] } - """); + """).ToStream(); OpenAIFileInfo openAIFileInfo = fileClient.UploadFile(document, "test-rag-file-delete-me.json", OpenAIFilePurpose.Assistants); diff --git a/.dotnet/tests/Samples/Assistants/Sample01_RetrievalAugmentedGenerationAsync.cs b/.dotnet/tests/Samples/Assistants/Sample01_RetrievalAugmentedGenerationAsync.cs index 4b706a8eb..061d0b3a1 100644 --- a/.dotnet/tests/Samples/Assistants/Sample01_RetrievalAugmentedGenerationAsync.cs +++ b/.dotnet/tests/Samples/Assistants/Sample01_RetrievalAugmentedGenerationAsync.cs @@ -13,12 +13,14 @@ public partial class AssistantSamples [Ignore("Compilation validation only")] public async Task Sample01_RetrievalAugmentedGenerationAsync() { + // Assistants is a beta API and subject to change; acknowledge its experimental status by suppressing the matching warning. +#pragma warning disable OPENAI001 OpenAIClient openAIClient = new(Environment.GetEnvironmentVariable("OpenAIClient_KEY")); FileClient fileClient = openAIClient.GetFileClient(); AssistantClient assistantClient = openAIClient.GetAssistantClient(); // First, let's contrive a document we'll use retrieval with and upload it. - BinaryData document = BinaryData.FromString(""" + using Stream document = BinaryData.FromString(""" { "description": "This document contains the sale history data for Contoso products.", "sales": [ @@ -45,7 +47,7 @@ public async Task Sample01_RetrievalAugmentedGenerationAsync() } ] } - """); + """).ToStream(); OpenAIFileInfo openAIFileInfo = await fileClient.UploadFileAsync(document, "test-rag-file-delete-me.json", OpenAIFilePurpose.Assistants); @@ -125,7 +127,7 @@ public async Task Sample01_RetrievalAugmentedGenerationAsync() OpenAIFileInfo imageInfo = await fileClient.GetFileInfoAsync(imageFileContent.FileId); BinaryData imageBytes = await fileClient.DownloadFileAsync(imageFileContent.FileId); using FileStream stream = File.OpenWrite($"{imageInfo.Filename}.png"); - imageBytes.ToStream().CopyTo(stream); + await imageBytes.ToStream().CopyToAsync(stream); Console.WriteLine($""); } diff --git a/.dotnet/tests/Samples/Assistants/Sample02_ListAssistantsWithPagination.cs b/.dotnet/tests/Samples/Assistants/Sample02_ListAssistantsWithPagination.cs index 59f171d66..4966ce1f8 100644 --- a/.dotnet/tests/Samples/Assistants/Sample02_ListAssistantsWithPagination.cs +++ b/.dotnet/tests/Samples/Assistants/Sample02_ListAssistantsWithPagination.cs @@ -10,6 +10,8 @@ public partial class AssistantSamples [Ignore("Compilation validation only")] public void Sample02_ListAssistantsWithPagination() { + // Assistants is a beta API and subject to change; acknowledge its experimental status by suppressing the matching warning. +#pragma warning disable OPENAI001 AssistantClient client = new(Environment.GetEnvironmentVariable("OpenAIClient_KEY")); string latestId = null; diff --git a/.dotnet/tests/Samples/Assistants/Sample02_ListAssistantsWithPaginationAsync.cs b/.dotnet/tests/Samples/Assistants/Sample02_ListAssistantsWithPaginationAsync.cs index 2e4f6db86..e834e5aa1 100644 --- a/.dotnet/tests/Samples/Assistants/Sample02_ListAssistantsWithPaginationAsync.cs +++ b/.dotnet/tests/Samples/Assistants/Sample02_ListAssistantsWithPaginationAsync.cs @@ -11,6 +11,8 @@ public partial class AssistantSamples [Ignore("Compilation validation only")] public async Task Sample02_ListAssistantsWithPaginationAsync() { + // Assistants is a beta API and subject to change; acknowledge its experimental status by suppressing the matching warning. +#pragma warning disable OPENAI001 AssistantClient client = new(Environment.GetEnvironmentVariable("OpenAIClient_KEY")); string latestId = null; diff --git a/.dotnet/tests/Samples/Assistants/Sample03_FunctionCalling.cs b/.dotnet/tests/Samples/Assistants/Sample03_FunctionCalling.cs index e989b95b2..7e5bca6a2 100644 --- a/.dotnet/tests/Samples/Assistants/Sample03_FunctionCalling.cs +++ b/.dotnet/tests/Samples/Assistants/Sample03_FunctionCalling.cs @@ -60,6 +60,8 @@ private static string GetCurrentWeather(string location, string unit = "celsius" [Ignore("Compilation validation only")] public void Sample03_FunctionCalling() { + // Assistants is a beta API and subject to change; acknowledge its experimental status by suppressing the matching warning. +#pragma warning disable OPENAI001 AssistantClient client = new(Environment.GetEnvironmentVariable("OpenAIClient_KEY")); #region diff --git a/.dotnet/tests/Samples/Assistants/Sample03_FunctionCallingAsync.cs b/.dotnet/tests/Samples/Assistants/Sample03_FunctionCallingAsync.cs index 44fc106b2..9333f51d0 100644 --- a/.dotnet/tests/Samples/Assistants/Sample03_FunctionCallingAsync.cs +++ b/.dotnet/tests/Samples/Assistants/Sample03_FunctionCallingAsync.cs @@ -14,6 +14,8 @@ public partial class AssistantSamples [Ignore("Compilation validation only")] public async Task Sample03_FunctionCallingAsync() { + // Assistants is a beta API and subject to change; acknowledge its experimental status by suppressing the matching warning. +#pragma warning disable OPENAI001 AssistantClient client = new(Environment.GetEnvironmentVariable("OpenAIClient_KEY")); #region diff --git a/.dotnet/tests/Samples/Chat/Sample05_ChatWithVision.cs b/.dotnet/tests/Samples/Chat/Sample05_ChatWithVision.cs index c5c696166..5803d9452 100644 --- a/.dotnet/tests/Samples/Chat/Sample05_ChatWithVision.cs +++ b/.dotnet/tests/Samples/Chat/Sample05_ChatWithVision.cs @@ -16,7 +16,7 @@ public void Sample05_ChatWithVision(Uri imageUri = null) List messages = [ new ChatRequestUserMessage( "Describe this image for me", - ChatMessageContent.CreateImage(imageUri)) + ChatMessageContent.FromImage(imageUri)) ]; ChatCompletion chatCompletion = client.CompleteChat(messages); diff --git a/.dotnet/tests/Samples/Chat/Sample05_ChatWithVisionAsync.cs b/.dotnet/tests/Samples/Chat/Sample05_ChatWithVisionAsync.cs index e180985d5..d3d5a5840 100644 --- a/.dotnet/tests/Samples/Chat/Sample05_ChatWithVisionAsync.cs +++ b/.dotnet/tests/Samples/Chat/Sample05_ChatWithVisionAsync.cs @@ -17,7 +17,7 @@ public async Task Sample05_ChatWithVisionAsync(Uri imageUri = null) List messages = [ new ChatRequestUserMessage( "Describe this image for me", - ChatMessageContent.CreateImage(imageUri)) + ChatMessageContent.FromImage(imageUri)) ]; ChatCompletion chatCompletion = await client.CompleteChatAsync(messages); diff --git a/.dotnet/tests/Samples/ClientSamples.cs b/.dotnet/tests/Samples/ClientSamples.cs index 21000a32b..fdbc3fd40 100644 --- a/.dotnet/tests/Samples/ClientSamples.cs +++ b/.dotnet/tests/Samples/ClientSamples.cs @@ -17,7 +17,9 @@ public void CreateAssistantAndFileClients() { OpenAIClient openAIClient = new(""); FileClient fileClient = openAIClient.GetFileClient(); +#pragma warning disable OPENAI001 AssistantClient assistantClient = openAIClient.GetAssistantClient(); +#pragma warning restore OPENAI001 } [Test] diff --git a/.dotnet/tests/Samples/CombinationSamples.cs b/.dotnet/tests/Samples/CombinationSamples.cs index d105b3839..eaac630de 100644 --- a/.dotnet/tests/Samples/CombinationSamples.cs +++ b/.dotnet/tests/Samples/CombinationSamples.cs @@ -23,7 +23,7 @@ public void AlpacaArtAssessor() { Style = ImageStyle.Vivid, Quality = ImageQuality.High, - Size = ImageSize.Size1792x1024, + Size = GeneratedImageSize.W1792xH1024, }); GeneratedImage imageGeneration = imageResult.Value; Console.WriteLine($"Majestic alpaca available at:\n{imageGeneration.ImageUri.AbsoluteUri}"); @@ -36,7 +36,7 @@ public void AlpacaArtAssessor() + "evaluate imagery, focus on criticizing elements of subject, composition, and other details."), new ChatRequestUserMessage( "describe the following image in a few sentences", - ChatMessageContent.CreateImage(imageGeneration.ImageUri)), + ChatMessageContent.FromImage(imageGeneration.ImageUri)), ], new ChatCompletionOptions() { @@ -108,7 +108,7 @@ public async Task CuriousCreatureCreator() description, new ImageGenerationOptions() { - Size = ImageSize.Size1792x1024, + Size = GeneratedImageSize.W1792xH1024, Quality = ImageQuality.High, }); Uri imageLocation = imageGenerationResult.Value.ImageUri; @@ -121,7 +121,7 @@ public async Task CuriousCreatureCreator() new ChatRequestSystemMessage("Assume the role of an art critic. Although usually cranky and occasionally even referred to as a 'curmudgeon', you're somehow entirely smitten with the subject presented to you and, despite your best efforts, can't help but lavish praise when you're asked to appraise a provided image."), new ChatRequestUserMessage( "Evaluate this image for me. What is it, and what do you think of it?", - ChatMessageContent.CreateImage(imageLocation)), + ChatMessageContent.FromImage(imageLocation)), ], new ChatCompletionOptions() { diff --git a/.dotnet/tests/Samples/Images/Sample01_SimpleImage.cs b/.dotnet/tests/Samples/Images/Sample01_SimpleImage.cs index 725e1cab3..5a9dcccbb 100644 --- a/.dotnet/tests/Samples/Images/Sample01_SimpleImage.cs +++ b/.dotnet/tests/Samples/Images/Sample01_SimpleImage.cs @@ -24,7 +24,7 @@ public void Sample01_SimpleImage() ImageGenerationOptions options = new() { Quality = ImageQuality.High, - Size = ImageSize.Size1792x1024, + Size = GeneratedImageSize.W1792xH1024, Style = ImageStyle.Vivid, ResponseFormat = ImageResponseFormat.Bytes }; diff --git a/.dotnet/tests/Samples/Images/Sample01_SimpleImageAsync.cs b/.dotnet/tests/Samples/Images/Sample01_SimpleImageAsync.cs index a3c55901a..16af4834a 100644 --- a/.dotnet/tests/Samples/Images/Sample01_SimpleImageAsync.cs +++ b/.dotnet/tests/Samples/Images/Sample01_SimpleImageAsync.cs @@ -25,7 +25,7 @@ public async Task Sample01_SimpleImageAsync() ImageGenerationOptions options = new() { Quality = ImageQuality.High, - Size = ImageSize.Size1792x1024, + Size = GeneratedImageSize.W1792xH1024, Style = ImageStyle.Vivid, ResponseFormat = ImageResponseFormat.Bytes }; diff --git a/.dotnet/tests/Samples/Images/Sample02_SimpleImageEdit.cs b/.dotnet/tests/Samples/Images/Sample02_SimpleImageEdit.cs index 174645ec2..570e44829 100644 --- a/.dotnet/tests/Samples/Images/Sample02_SimpleImageEdit.cs +++ b/.dotnet/tests/Samples/Images/Sample02_SimpleImageEdit.cs @@ -13,23 +13,26 @@ public void Sample02_SimpleImageEdit() { ImageClient client = new("dall-e-2", Environment.GetEnvironmentVariable("OpenAIClient_KEY")); - string imagePath = Path.Combine("Assets", "edit_sample_image.png"); - BinaryData imageBytes = BinaryData.FromBytes(File.ReadAllBytes(imagePath)); + string imageFileName = "edit_sample_image.png"; + string imagePath = Path.Combine("Assets", imageFileName); + using Stream inputImage = File.OpenRead(imagePath); string prompt = "An inflatable flamingo float in a pool"; - string maskPath = Path.Combine("Assets", "edit_sample_mask.png"); - BinaryData maskBytes = BinaryData.FromBytes(File.ReadAllBytes(maskPath)); + string maskFileName = "edit_sample_mask.png"; + string maskPath = Path.Combine("Assets", maskFileName); + using Stream mask = File.OpenRead(maskPath); ImageEditOptions options = new() { - MaskBytes = maskBytes, - Size = ImageSize.Size1024x1024, + Mask = mask, + MaskFileName = maskFileName, + Size = GeneratedImageSize.W1024xH1024, ResponseFormat = ImageResponseFormat.Bytes }; - GeneratedImageCollection image = client.GenerateImageEdits(imageBytes, prompt, 1, options); - BinaryData bytes = image[0].ImageBytes; + GeneratedImageCollection images = client.GenerateImageEdits(inputImage, imageFileName, prompt, 1, options); + BinaryData bytes = images[0].ImageBytes; using FileStream stream = File.OpenWrite($"{Guid.NewGuid()}.png"); bytes.ToStream().CopyTo(stream); diff --git a/.dotnet/tests/Samples/Images/Sample02_SimpleImageEditAsync.cs b/.dotnet/tests/Samples/Images/Sample02_SimpleImageEditAsync.cs index ac8b7373d..e389c339a 100644 --- a/.dotnet/tests/Samples/Images/Sample02_SimpleImageEditAsync.cs +++ b/.dotnet/tests/Samples/Images/Sample02_SimpleImageEditAsync.cs @@ -14,26 +14,29 @@ public async Task Sample02_SimpleImageEditAsync() { ImageClient client = new("dall-e-2", Environment.GetEnvironmentVariable("OpenAIClient_KEY")); - string imagePath = Path.Combine("Assets", "edit_sample_image.png"); - BinaryData imageBytes = BinaryData.FromBytes(File.ReadAllBytes(imagePath)); + string imageFileName = "edit_sample_image.png"; + string imagePath = Path.Combine("Assets", imageFileName); + using Stream inputImage = File.OpenRead(imagePath); string prompt = "An inflatable flamingo float in a pool"; - string maskPath = Path.Combine("Assets", "edit_sample_mask.png"); - BinaryData maskBytes = BinaryData.FromBytes(File.ReadAllBytes(maskPath)); + string maskFileName = "edit_sample_mask.png"; + string maskPath = Path.Combine("Assets", maskFileName); + using Stream mask = File.OpenRead(maskPath); ImageEditOptions options = new() { - MaskBytes = maskBytes, - Size = ImageSize.Size1024x1024, + Mask = mask, + MaskFileName = maskFileName, + Size = GeneratedImageSize.W1024xH1024, ResponseFormat = ImageResponseFormat.Bytes }; - GeneratedImageCollection image = await client.GenerateImageEditsAsync(imageBytes, prompt, 1, options); - BinaryData bytes = image[0].ImageBytes; + GeneratedImageCollection images = await client.GenerateImageEditsAsync(inputImage, imageFileName, prompt, 1, options); + BinaryData bytes = images[0].ImageBytes; using FileStream stream = File.OpenWrite($"{Guid.NewGuid()}.png"); - bytes.ToStream().CopyTo(stream); + await bytes.ToStream().CopyToAsync(stream); } } } diff --git a/.dotnet/tests/Samples/Images/Sample03_SimpleImageVariation.cs b/.dotnet/tests/Samples/Images/Sample03_SimpleImageVariation.cs index 1ec803cd4..a590123ea 100644 --- a/.dotnet/tests/Samples/Images/Sample03_SimpleImageVariation.cs +++ b/.dotnet/tests/Samples/Images/Sample03_SimpleImageVariation.cs @@ -14,16 +14,16 @@ public void Sample03_SimpleImageVariation() ImageClient client = new("dall-e-2", Environment.GetEnvironmentVariable("OpenAIClient_KEY")); string imagePath = Path.Combine("Assets", "variation_sample_image.png"); - BinaryData imageBytes = BinaryData.FromBytes(File.ReadAllBytes(imagePath)); + using FileStream inputImage = File.OpenRead(imagePath); ImageVariationOptions options = new() { - Size = ImageSize.Size1024x1024, + Size = GeneratedImageSize.W1024xH1024, ResponseFormat = ImageResponseFormat.Bytes }; - GeneratedImageCollection image = client.GenerateImageVariations(imageBytes, 1, options); - BinaryData bytes = image[0].ImageBytes; + GeneratedImageCollection images = client.GenerateImageVariations(inputImage, 1, options); + BinaryData bytes = images[0].ImageBytes; using FileStream stream = File.OpenWrite($"{Guid.NewGuid()}.png"); bytes.ToStream().CopyTo(stream); diff --git a/.dotnet/tests/Samples/Images/Sample03_SimpleImageVariationAsync.cs b/.dotnet/tests/Samples/Images/Sample03_SimpleImageVariationAsync.cs index 6f2aba398..58f708dde 100644 --- a/.dotnet/tests/Samples/Images/Sample03_SimpleImageVariationAsync.cs +++ b/.dotnet/tests/Samples/Images/Sample03_SimpleImageVariationAsync.cs @@ -15,16 +15,16 @@ public async Task Sample03_SimpleImageVariationAsync() ImageClient client = new("dall-e-2", Environment.GetEnvironmentVariable("OpenAIClient_KEY")); string imagePath = Path.Combine("Assets", "variation_sample_image.png"); - BinaryData imageBytes = BinaryData.FromBytes(File.ReadAllBytes(imagePath)); + using FileStream inputImage = File.OpenRead(imagePath); ImageVariationOptions options = new() { - Size = ImageSize.Size1024x1024, + Size = GeneratedImageSize.W1024xH1024, ResponseFormat = ImageResponseFormat.Bytes }; - GeneratedImageCollection image = await client.GenerateImageVariationsAsync(imageBytes, 1, options); - BinaryData bytes = image[0].ImageBytes; + GeneratedImageCollection images = await client.GenerateImageVariationsAsync(inputImage, 1, options); + BinaryData bytes = images[0].ImageBytes; using FileStream stream = File.OpenWrite($"{Guid.NewGuid()}.png"); bytes.ToStream().CopyTo(stream); diff --git a/.dotnet/tests/TestScenarios/AssistantTests.cs b/.dotnet/tests/TestScenarios/AssistantTests.cs index 696d6490b..aa19fc2d8 100644 --- a/.dotnet/tests/TestScenarios/AssistantTests.cs +++ b/.dotnet/tests/TestScenarios/AssistantTests.cs @@ -9,6 +9,7 @@ namespace OpenAI.Tests.Assistants; +#pragma warning disable OPENAI001 public partial class AssistantTests { [Test] @@ -122,7 +123,7 @@ public async Task BasicFunctionToolWorks() Assert.That(requiredFunctionToolCall, Is.Not.Null); _ = await client.SubmitToolOutputsAsync(threadResult.Value.Id, runResult.Value.Id, [ - new ToolOutput(requiredFunctionToolCall, "tacos"), + new ToolOutput(requiredFunctionToolCall.Id, "tacos"), ]); runResult = await client.GetRunAsync(threadResult.Value.Id, runResult.Value.Id); Assert.That(runResult.Value.Status, Is.Not.EqualTo(RunStatus.RequiresAction)); @@ -244,7 +245,7 @@ private async Task CreateCommonTestAssistantAsync() [TearDown] protected async Task DeleteRecentTestThings() { - AssistantClient client = new(); + AssistantClient client = GetTestClient(); foreach(Assistant assistant in client.GetAssistants().Value) { if (assistant.Name == s_testAssistantName @@ -260,3 +261,5 @@ protected async Task DeleteRecentTestThings() private static readonly string s_testAssistantName = $".NET SDK Test Assistant - Please Delete Me"; private static readonly string s_cleanupMetadataKey = $"test_metadata_cleanup_eligible"; } + +#pragma warning restore OPENAI001 diff --git a/.dotnet/tests/TestScenarios/ChatWithVision.cs b/.dotnet/tests/TestScenarios/ChatWithVision.cs index fe6318bf0..477b48772 100644 --- a/.dotnet/tests/TestScenarios/ChatWithVision.cs +++ b/.dotnet/tests/TestScenarios/ChatWithVision.cs @@ -13,8 +13,8 @@ public partial class ChatWithVision [Test] public void DescribeAnImage() { - var stopSignPath = Path.Combine("Assets", "stop_sign.png"); - var stopSignData = BinaryData.FromBytes(File.ReadAllBytes(stopSignPath)); + string stopSignPath = Path.Combine("Assets", "stop_sign.png"); + using Stream stopSignStream = File.OpenRead(stopSignPath); ChatClient client = GetTestClient(TestScenario.VisionChat); @@ -22,7 +22,7 @@ public void DescribeAnImage() [ new ChatRequestUserMessage( "Describe this image for me", - ChatMessageContent.CreateImage(stopSignData, "image/png")), + ChatMessageContent.FromImage(stopSignStream, "image/png")), ], new ChatCompletionOptions() { MaxTokens = 2048, diff --git a/.dotnet/tests/TestScenarios/FileClientTests.cs b/.dotnet/tests/TestScenarios/FileClientTests.cs index e7b64f302..c7c3875ae 100644 --- a/.dotnet/tests/TestScenarios/FileClientTests.cs +++ b/.dotnet/tests/TestScenarios/FileClientTests.cs @@ -2,6 +2,7 @@ using OpenAI.Files; using System; using System.ClientModel; +using System.IO; using static OpenAI.Tests.TestHelpers; namespace OpenAI.Tests.Files; @@ -25,16 +26,24 @@ public void ListFilesWorks() public void UploadFileWorks() { FileClient client = GetTestClient(); - BinaryData uploadData = BinaryData.FromString("hello, this is a text file, please delete me"); + using Stream uploadData = BinaryData.FromString("hello, this is a text file, please delete me").ToStream(); + ClientResult uploadResult = client.UploadFile(uploadData, "test-file-delete-me.txt", OpenAIFilePurpose.Assistants); + Assert.That(uploadResult.Value, Is.Not.Null); + + ClientResult fileInfoResult = client.GetFileInfo(uploadResult.Value.Id); + Assert.AreEqual(uploadResult.Value.Id, fileInfoResult.Value.Id); + Assert.AreEqual(uploadResult.Value.Filename, fileInfoResult.Value.Filename); } [Test] public void DownloadAndInfoWork() { FileClient client = GetTestClient(); + ClientResult fileInfoResult = client.GetFileInfo("file-S7roYWamZqfMK9D979HU4q6m"); Assert.That(fileInfoResult.Value, Is.Not.Null); + ClientResult downloadResult = client.DownloadFile("file-S7roYWamZqfMK9D979HU4q6m"); Assert.That(downloadResult.Value, Is.Not.Null); } diff --git a/.dotnet/tests/TestScenarios/TranscriptionTests.cs b/.dotnet/tests/TestScenarios/TranscriptionTests.cs index 70753d47d..ed002e5b6 100644 --- a/.dotnet/tests/TestScenarios/TranscriptionTests.cs +++ b/.dotnet/tests/TestScenarios/TranscriptionTests.cs @@ -1,6 +1,5 @@ using NUnit.Framework; using OpenAI.Audio; -using System; using System.ClientModel; using System.IO; using static OpenAI.Tests.TestHelpers; @@ -14,8 +13,7 @@ public void BasicTranscriptionWorks() { AudioClient client = GetTestClient(); using FileStream inputStream = File.OpenRead(Path.Combine("Assets", "hello_world.m4a")); - BinaryData inputData = BinaryData.FromStream(inputStream); - ClientResult transcriptionResult = client.TranscribeAudio(inputData, "hello_world.m4a"); + ClientResult transcriptionResult = client.TranscribeAudio(inputStream, "hello_world.m4a"); Assert.That(transcriptionResult.Value, Is.Not.Null); Assert.That(transcriptionResult.Value.Text.ToLowerInvariant(), Contains.Substring("hello")); } @@ -25,12 +23,11 @@ public void WordTimestampsWork() { AudioClient client = GetTestClient(); using FileStream inputStream = File.OpenRead(Path.Combine("Assets", "hello_world.m4a")); - BinaryData inputData = BinaryData.FromStream(inputStream); - ClientResult transcriptionResult = client.TranscribeAudio(inputData, "hello_world.m4a", new AudioTranscriptionOptions() + ClientResult transcriptionResult = client.TranscribeAudio(inputStream, "hello_world.m4a", new AudioTranscriptionOptions() { EnableWordTimestamps = true, EnableSegmentTimestamps = true, - ResponseFormat = AudioTranscriptionFormat.Detailed, + ResponseFormat = AudioTranscriptionFormat.Verbose, }); Assert.That(transcriptionResult.Value, Is.Not.Null); // Assert.That(transcriptionResult.Value.Segments, Is.Null); diff --git a/.dotnet/tests/TestScenarios/TranslationTests.cs b/.dotnet/tests/TestScenarios/TranslationTests.cs index f035a87e5..8d2842dea 100644 --- a/.dotnet/tests/TestScenarios/TranslationTests.cs +++ b/.dotnet/tests/TestScenarios/TranslationTests.cs @@ -1,6 +1,5 @@ using NUnit.Framework; using OpenAI.Audio; -using System; using System.ClientModel; using System.IO; using static OpenAI.Tests.TestHelpers; @@ -13,9 +12,8 @@ public partial class TranslationTests public void BasicTranslationWorks() { AudioClient client = GetTestClient(); - using FileStream inputStream = File.OpenRead(Path.Combine("Assets", "hola_mundo.m4a")); - BinaryData inputData = BinaryData.FromStream(inputStream); - ClientResult translationResult = client.TranslateAudio(inputData, "hola_mundo.m4a"); + using FileStream inputStream = File.OpenRead(Path.Combine("Assets", "multilingual.wav")); + ClientResult translationResult = client.TranslateAudio(inputStream, "multilingual.wav"); Assert.That(translationResult.Value, Is.Not.Null); // Assert.That(translationResult.Value.Text.ToLowerInvariant(), Contains.Substring("hello")); } diff --git a/.dotnet/tests/Utility/TestHelpers.cs b/.dotnet/tests/Utility/TestHelpers.cs index a9ec4198b..bfc847e42 100644 --- a/.dotnet/tests/Utility/TestHelpers.cs +++ b/.dotnet/tests/Utility/TestHelpers.cs @@ -36,12 +36,14 @@ public static T GetTestClient(TestScenario scenario, string overrideModel = n options.ErrorOptions = throwOnError ? ClientErrorBehaviors.Default : ClientErrorBehaviors.NoThrow; object clientObject = scenario switch { - TestScenario.Chat => new ChatClient(overrideModel ?? "gpt-3.5-turbo", options), - TestScenario.VisionChat => new ChatClient(overrideModel ?? "gpt-4-vision-preview", options), - TestScenario.Assistants => new AssistantClient(options), - TestScenario.Images => new ImageClient(overrideModel ?? "dall-e-3", options), - TestScenario.Files => new FileClient(options), - TestScenario.Transcription => new AudioClient(overrideModel ?? "whisper-1", options), + TestScenario.Chat => new ChatClient(overrideModel ?? "gpt-3.5-turbo", credential: null, options), + TestScenario.VisionChat => new ChatClient(overrideModel ?? "gpt-4-vision-preview", credential: null, options), +#pragma warning disable OPENAI001 + TestScenario.Assistants => new AssistantClient(credential: null, options), +#pragma warning restore OPENAI001 + TestScenario.Images => new ImageClient(overrideModel ?? "dall-e-3", credential: null, options), + TestScenario.Files => new FileClient(credential: null, options), + TestScenario.Transcription => new AudioClient(overrideModel ?? "whisper-1", credential: null, options), _ => throw new NotImplementedException(), }; return (T)clientObject; diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f296f9c90..8e25a5651 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,16 +1,16 @@ name: Release package on: - workflow_dispatch: release: - types: [created] + types: [published] + jobs: deploy: runs-on: ubuntu-latest permissions: packages: write - contents: read + contents: write steps: - uses: actions/checkout@v4 - uses: actions/setup-dotnet@v3 @@ -32,13 +32,23 @@ jobs: SECRET_VALUE: ${{ secrets.OPENAI_TOKEN }} working-directory: .dotnet + # Pack the client nuget package and include urls back to the repository and release tag - name: Pack run: dotnet pack --no-build --configuration Release --output "${{github.workspace}}/artifacts/packages" + /p:RepositoryUrl="https://github.com/${{ github.repository }}" + /p:PackageProjectUrl="https://github.com/${{ github.repository }}/tree/${{ github.event.release.tag_name }}" working-directory: .dotnet + # Append the nuget package to the github release that triggered this workflow + - name: Upload release asset + run: gh release upload ${{ github.event.release.tag_name }} + ${{github.workspace}}/artifacts/packages/*.nupkg + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Upload artifact uses: actions/upload-artifact@v2 with: diff --git a/README.md b/README.md index eff608de4..0f9e25d5f 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # A conversion of the OpenAI OpenAPI to TypeSpec -Snapshot: https://raw.githubusercontent.com/openai/openai-openapi/b648b7823135e6fa5148ac9a303c16fdad050da6/openapi.yaml +Snapshot: https://raw.githubusercontent.com/openai/openai-openapi/28a300c5784415af66f81b2acc0db182f6eb3bbd/openapi.yaml There are some deltas: diff --git a/audio/models.tsp b/audio/models.tsp index 53d7757c3..13d7301ce 100644 --- a/audio/models.tsp +++ b/audio/models.tsp @@ -23,8 +23,10 @@ model CreateSpeechRequest { */ voice: "alloy" | "echo" | "fable" | "onyx" | "nova" | "shimmer"; - /** The format to audio in. Supported formats are `mp3`, `opus`, `aac`, and `flac`. */ - response_format?: "mp3" | "opus" | "aac" | "flac" = "mp3"; + /** + * The format to audio in. Supported formats are `mp3`, `opus`, `aac`, `flac`, `wav`, and `pcm`. + */ + response_format?: "mp3" | "opus" | "aac" | "flac" | "wav" | "pcm" = "mp3"; /** * The speed of the generated audio. Select a value from `0.25` to `4.0`. `1.0` is the default. @@ -43,7 +45,10 @@ model CreateTranscriptionRequest { @extension("x-oaiTypeLabel", "file") file: bytes; - /** ID of the model to use. Only `whisper-1` is currently available. */ + /** + * ID of the model to use. Only `whisper-1` (which is powered by our open source Whisper V2 model) + * is currently available. + */ @extension("x-oaiTypeLabel", "string") `model`: string | SPEECH_TO_TEXT_MODELS; @@ -66,16 +71,24 @@ model CreateTranscriptionRequest { */ response_format?: "json" | "text" | "srt" | "verbose_json" | "vtt" = "json"; + // TODO: Min and max values are absent in the OpenAPI spec but mentioned in the description. /** * The sampling temperature, between 0 and 1. Higher values like 0.8 will make the output more * random, while lower values like 0.2 will make it more focused and deterministic. If set to 0, * the model will use [log probability](https://en.wikipedia.org/wiki/Log_probability) to * automatically increase the temperature until certain thresholds are hit. */ - // NOTE: Min and max values are absent in the OpenAPI spec but mentioned in the description. @minValue(0) @maxValue(1) temperature?: float64 = 0; + + /** + * The timestamp granularities to populate for this transcription. `response_format` must be set + * `verbose_json` to use timestamp granularities. Either or both of these options are supported: + * `word`, or `segment`. Note: There is no additional latency for segment timestamps, but + * generating word timestamps incurs additional latency. + */ + timestamp_granularities?: TimestampGranularities[] = [ "segment" ] } model CreateTranslationRequest { @@ -87,7 +100,10 @@ model CreateTranslationRequest { @extension("x-oaiTypeLabel", "file") file: bytes; - /** ID of the model to use. Only `whisper-1` is currently available. */ + /** + * ID of the model to use. Only `whisper-1` (which is powered by our open source Whisper V2 model) + * is currently available. + */ @extension("x-oaiTypeLabel", "string") `model`: string | SPEECH_TO_TEXT_MODELS; @@ -97,69 +113,79 @@ model CreateTranslationRequest { */ prompt?: string; - // NOTE: this is just string in the actual API? + // TODO: This is defined as a plain string in the OpenAPI spec instead of an enum. /** * The format of the transcript output, in one of these options: json, text, srt, verbose_json, or * vtt. */ response_format?: "json" | "text" | "srt" | "verbose_json" | "vtt" = "json"; + // TODO: Min and max values are absent in the OpenAPI spec but mentioned in the description. /** * The sampling temperature, between 0 and 1. Higher values like 0.8 will make the output more * random, while lower values like 0.2 will make it more focused and deterministic. If set to 0, * the model will use [log probability](https://en.wikipedia.org/wiki/Log_probability) to * automatically increase the temperature until certain thresholds are hit. */ - // NOTE: Min and max values are absent in the OpenAPI spec but mentioned in the description. @minValue(0) @maxValue(1) temperature?: float64 = 0; } -// NOTE: This model is not defined in the OpenAI API spec. -model CreateTranscriptionResponse { - /** The transcribed text for the provided audio data. */ +/** Represents a transcription response returned by model, based on the provided input. */ +model CreateTranscriptionResponseJson { + /** The transcribed text. */ text: string; +} - /** The label that describes which operation type generated the accompanying response data. */ - task?: "transcribe"; +/** + * Represents a verbose json transcription response returned by model, based on the provided input. + */ +model CreateTranscriptionResponseVerboseJson { + // TODO: This is not defined in the OpenAPI spec. + /** The task label. */ + task: "transcribe"; - /** The spoken language that was detected in the audio data. */ - language?: string; + /** The language of the input audio. */ + language: string; - /** - * The total duration of the audio processed to produce accompanying transcription information. - */ + /** The duration of the input audio. */ @encode("seconds", float64) - duration?: duration; + duration: duration; - /** - * A collection of information about the timing, probabilities, and other detail of each processed - * audio segment. - */ - segments?: AudioSegment[]; + /** The transcribed text. */ + text: string; + + /** Extracted words and their corresponding timestamps. */ + words?: TranscriptionWord[]; + + /** Segments of the transcribed text and their corresponding details. */ + segments?: TranscriptionSegment[]; } -// NOTE: This model is not defined in the OpenAI API spec. -model CreateTranslationResponse { - /** The translated text for the provided audio data. */ +model CreateTranslationResponseJson { text: string; +} - /** The label that describes which operation type generated the accompanying response data. */ - task?: "translate"; +model CreateTranslationResponseVerboseJson { + // TODO: This is not defined in the OpenAPI spec. + /** The task label. */ + task: "translate"; - /** The spoken language that was detected in the audio data. */ - language?: string; + /** The language of the output translation (always `english`). */ + language: string; - /** The total duration of the audio processed to produce accompanying translation information. */ + /** The duration of the input audio. */ @encode("seconds", float64) - duration?: duration; + duration: duration; - /** - * A collection of information about the timing, probabilities, and other detail of each processed - * audio segment. - */ - segments?: AudioSegment[]; + /** The translated text. */ + text: string; + + // TODO: The OpenAPI spec reuses the TranscriptionSegment definition here even though this belongs + // to the translation response. + /** Segments of the translated text and their corresponding details. */ + segments?: TranscriptionSegment[]; } alias TEXT_TO_SPEECH_MODELS = @@ -169,45 +195,64 @@ alias TEXT_TO_SPEECH_MODELS = alias SPEECH_TO_TEXT_MODELS = | "whisper-1"; -// NOTE: This model is not defined in the OpenAI API spec. -model AudioSegment { - /** The zero-based index of this segment. */ +alias TimestampGranularities = + | "word" + | "segment"; + +model TranscriptionSegment { + /** Unique identifier of the segment. */ id: safeint; - /** - * The seek position associated with the processing of this audio segment. Seek positions are - * expressed as hundredths of seconds. The model may process several segments from a single seek - * position, so while the seek position will never represent a later time than the segment's - * start, the segment's start may represent a significantly later time than the segment's - * associated seek position. - */ + /** Seek offset of the segment. */ seek: safeint; - /** The time at which this segment started relative to the beginning of the audio. */ + /** Start time of the segment in seconds. */ @encode("seconds", float64) start: duration; - /** The time at which this segment ended relative to the beginning of the audio. */ + /** End time of the segment in seconds. */ @encode("seconds", float64) end: duration; - /** The text that was part of this audio segment. */ + /** Text content of the segment. */ text: string; - /** The token IDs matching the text in this audio segment. */ + /** Array of token IDs for the text content. */ tokens: TokenArray; - /** The temperature score associated with this audio segment. */ + // TODO: Min and max values are not defined in the OpenAPI spec. + /** Temperature parameter used for generating the segment. */ @minValue(0) @maxValue(1) temperature: float64; - /** The average log probability associated with this audio segment. */ + /** + * Average logprob of the segment. If the value is lower than -1, consider the logprobs failed. + */ avg_logprob: float64; - /** The compression ratio of this audio segment. */ + /** + * Compression ratio of the segment. If the value is greater than 2.4, consider the compression + * failed. + */ compression_ratio: float64; - /** The probability of no speech detection within this audio segment. */ + /** + * Probability of no speech in the segment. If the value is higher than 1.0 and the `avg_logprob` + * is below -1, consider this segment silent. + */ no_speech_prob: float64; +} + +model TranscriptionWord { + /** The text content of the word. */ + word: string; + + /** Start time of the word in seconds. */ + @encode("seconds", float64) + start: duration; + + /** End time of the word in seconds. */ + @encode("seconds", float64) + end: duration; } \ No newline at end of file diff --git a/audio/operations.tsp b/audio/operations.tsp index ee1cb428e..eafe1aef0 100644 --- a/audio/operations.tsp +++ b/audio/operations.tsp @@ -35,9 +35,10 @@ interface Audio { @header contentType: "multipart/form-data", @body audio: CreateTranscriptionRequest, ): - | CreateTranscriptionResponse + | CreateTranscriptionResponseVerboseJson + | CreateTranscriptionResponseJson | { - // TODO: Is this the appropriate way to describe the multiple possible response types? + // TODO: This response is not defined in the OpenAPI spec. @header contentType: "text/plain"; @body text: string; } @@ -51,10 +52,11 @@ interface Audio { createTranslation( @header contentType: "multipart/form-data", @body audio: CreateTranslationRequest, - ): - | CreateTranslationResponse + ): + | CreateTranslationResponseVerboseJson + | CreateTranslationResponseJson | { - // TODO: Is this the appropriate way to describe the multiple possible response types? + // TODO: This response is not defined in the OpenAPI spec. @header contentType: "text/plain"; @body text: string; } diff --git a/chat/models.tsp b/chat/models.tsp index 160afb122..1c357afe5 100644 --- a/chat/models.tsp +++ b/chat/models.tsp @@ -50,12 +50,12 @@ model CreateChatCompletionRequest { logprobs?: boolean | null = false; /** - * An integer between 0 and 5 specifying the number of most likely tokens to return at each token + * An integer between 0 and 20 specifying the number of most likely tokens to return at each token * position, each with an associated log probability. `logprobs` must be set to `true` if this * parameter is used. */ @minValue(0) - @maxValue(5) + @maxValue(20) top_logprobs?: safeint | null; /** @@ -87,9 +87,10 @@ model CreateChatCompletionRequest { @maxValue(2) presence_penalty?: float64 | null = 0; - /** + /** * An object specifying the format that the model must output. Compatible with - * [GPT-4 Turbo](/docs/models/gpt-4-and-gpt-4-turbo) and `gpt-3.5-turbo-1106`. + * [GPT-4 Turbo](/docs/models/gpt-4-and-gpt-4-turbo) and all GPT-3.5 Turbo models newer than + * `gpt-3.5-turbo-1106`. * * Setting to `{ "type": "json_object" }` enables JSON mode, which guarantees the message the * model generates is valid JSON. @@ -262,6 +263,7 @@ alias CHAT_COMPLETION_MODELS = | "gpt-3.5-turbo-0301" | "gpt-3.5-turbo-0613" | "gpt-3.5-turbo-1106" + | "gpt-3.5-turbo-0125" | "gpt-3.5-turbo-16k-0613"; @oneOf @@ -358,8 +360,8 @@ model ChatCompletionRequestMessageContentPartImage { type: "image_url"; image_url: { + // TODO: The OpenAPI spec only describes this as a URL. /** Either a URL of the image or the base64 encoded image data. */ - // TODO: The original OpenAPI spec only describes this as a URL. url: url | string; /** @@ -521,7 +523,10 @@ model ChatCompletionTokenLogprob { /** The token. */ token: string; - /** The log probability of this token. */ + /** + * The log probability of this token, if it is within the top 20 most likely tokens. Otherwise, + * the value `-9999.0` is used to signify that the token is very unlikely. + */ logprob: float64; /** @@ -555,7 +560,7 @@ model ChatCompletionTokenLogprob { /** * Specifying a particular function via `{"name": "my_function"}` forces the model to call that - * function. + * function. */ model ChatCompletionFunctionCallOption { /** The name of the function to call. */ diff --git a/files/operations.tsp b/files/operations.tsp index 94320fa3d..0a1336ad5 100644 --- a/files/operations.tsp +++ b/files/operations.tsp @@ -34,8 +34,8 @@ interface Files { @tag("Files") @summary("Returns a list of files that belong to the user's organization.") listFiles( + // TODO: This is just a string in the OpenAPI spec. /** Only return files with the given purpose. */ - // NOTE: This is just a string in the OpenAPI spec. @query purpose?: string, ): ListFilesResponse | ErrorResponse; @@ -64,8 +64,10 @@ interface Files { @operationId("downloadFile") @tag("Files") @summary("Returns the contents of the specified file.") + // TODO: The OpenAPI spec says this returns a plain string but that the Content-Type is + // application/json and not text/plain. downloadFile( /** The ID of the file to use for this request. */ @path file_id: string, - ): string | ErrorResponse; // TODO: The OpenAPI spec says this is a string but the Content-Type is application/json? + ): string | ErrorResponse; } diff --git a/images/models.tsp b/images/models.tsp index e308ade23..30c56c44a 100644 --- a/images/models.tsp +++ b/images/models.tsp @@ -22,14 +22,17 @@ model CreateImageRequest { // TODO: This is generated as a "oneOf" in the tsp-output? n?: ImagesN | null = 1; + // TODO: This is not marked as nullable in the OpenAPI spec. /** * The quality of the image that will be generated. `hd` creates images with finer details and * greater consistency across the image. This param is only supported for `dall-e-3`. */ - // NOTE: This is not marked as nullable in the OpenAPI spec. quality?: "standard" | "hd" | null = "standard"; - /** The format in which the generated images are returned. Must be one of `url` or `b64_json`. */ + /** + * The format in which the generated images are returned. Must be one of `url` or `b64_json`. URLs + * are only valid for 60 minutes after the image has been generated. + */ response_format?: "url" | "b64_json" | null = "url"; /** @@ -60,8 +63,8 @@ model CreateImageEditRequest { @encode("binary") image: bytes; + // TODO: Max length is not defined in the OpenAPI spec but mentioned in the description. /** A text description of the desired image(s). The maximum length is 1000 characters. */ - // NOTE: Max length is not defined in the OpenAI spec but mentioned in the description. @maxLength(1000) prompt: string; diff --git a/models/models.tsp b/models/models.tsp index f522ca893..a1e1d62ec 100644 --- a/models/models.tsp +++ b/models/models.tsp @@ -10,7 +10,7 @@ model ListModelsResponse { model DeleteModelResponse { id: string; deleted: boolean; - object: "model"; // NOTE: This is just a string in the OpenAPI spec, no enum. + object: "model"; // TODO: This is just a string in the OpenAPI spec, not an enum. } /** Describes an OpenAI model offering that can be used with the API. */ diff --git a/moderations/models.tsp b/moderations/models.tsp index b844b2659..2423302d3 100644 --- a/moderations/models.tsp +++ b/moderations/models.tsp @@ -17,9 +17,7 @@ model CreateModerationRequest { `model`?: string | MODERATION_MODELS = "text-moderation-latest"; } -/** - * Represents policy compliance report by OpenAI's content moderation model against a given input. - */ +/** Represents if a given text input is potentially harmful. */ model CreateModerationResponse { /** The unique identifier for the moderation request. */ id: string; @@ -29,7 +27,7 @@ model CreateModerationResponse { /** A list of moderation objects. */ results: { - /** Whether the content violates [OpenAI's usage policies](/policies/usage-policies). */ + /** Whether any of the below categories are flagged. */ flagged: boolean; /** A list of the categories, and whether they are flagged or not. */ diff --git a/moderations/operations.tsp b/moderations/operations.tsp index 7760ec2b2..f73b54375 100644 --- a/moderations/operations.tsp +++ b/moderations/operations.tsp @@ -14,7 +14,7 @@ interface Moderations { @post @operationId("createModeration") @tag("Moderations") - @summary("Classifies if text violates OpenAI's Content Policy") + @summary("Classifies if text is potentially harmful.") createModeration( @body content: CreateModerationRequest, ): CreateModerationResponse | ErrorResponse; diff --git a/openapi.yaml b/openapi.yaml index a6e16ee12..6d3ecae31 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -32,7 +32,7 @@ tags: - name: Models description: List and describe the various models available in the API. - name: Moderations - description: Given a input text, outputs if the model classifies it as violating OpenAI's content policy. + description: Given a input text, outputs if the model classifies it as potentially harmful. paths: # Note: When adding an endpoint, make sure you also add it in the `groups` section, in the end of this file, # under the appropriate group @@ -115,7 +115,7 @@ paths: "id": "chatcmpl-123", "object": "chat.completion", "created": 1677652288, - "model": "gpt-3.5-turbo-0613", + "model": "gpt-3.5-turbo-0125", "system_fingerprint": "fp_44709d6fcb", "choices": [{ "index": 0, @@ -212,7 +212,7 @@ paths: "id": "chatcmpl-123", "object": "chat.completion", "created": 1677652288, - "model": "gpt-3.5-turbo-0613", + "model": "gpt-3.5-turbo-0125", "system_fingerprint": "fp_44709d6fcb", "choices": [{ "index": 0, @@ -287,19 +287,13 @@ paths: main(); response: &chat_completion_chunk_example | - {"id":"chatcmpl-123","object":"chat.completion.chunk","created":1694268190,"model":"gpt-3.5-turbo-0613", "system_fingerprint": "fp_44709d6fcb", "choices":[{"index":0,"delta":{"role":"assistant","content":""},"logprobs":null,"finish_reason":null}]} + {"id":"chatcmpl-123","object":"chat.completion.chunk","created":1694268190,"model":"gpt-3.5-turbo-0125", "system_fingerprint": "fp_44709d6fcb", "choices":[{"index":0,"delta":{"role":"assistant","content":""},"logprobs":null,"finish_reason":null}]} - {"id":"chatcmpl-123","object":"chat.completion.chunk","created":1694268190,"model":"gpt-3.5-turbo-0613", "system_fingerprint": "fp_44709d6fcb", "choices":[{"index":0,"delta":{"content":"Hello"},"logprobs":null,"finish_reason":null}]} - - {"id":"chatcmpl-123","object":"chat.completion.chunk","created":1694268190,"model":"gpt-3.5-turbo-0613", "system_fingerprint": "fp_44709d6fcb", "choices":[{"index":0,"delta":{"content":"!"},"logprobs":null,"finish_reason":null}]} + {"id":"chatcmpl-123","object":"chat.completion.chunk","created":1694268190,"model":"gpt-3.5-turbo-0125", "system_fingerprint": "fp_44709d6fcb", "choices":[{"index":0,"delta":{"content":"Hello"},"logprobs":null,"finish_reason":null}]} .... - {"id":"chatcmpl-123","object":"chat.completion.chunk","created":1694268190,"model":"gpt-3.5-turbo-0613", "system_fingerprint": "fp_44709d6fcb", "choices":[{"index":0,"delta":{"content":" today"},"logprobs":null,"finish_reason":null}]} - - {"id":"chatcmpl-123","object":"chat.completion.chunk","created":1694268190,"model":"gpt-3.5-turbo-0613", "system_fingerprint": "fp_44709d6fcb", "choices":[{"index":0,"delta":{"content":"?"},"logprobs":null,"finish_reason":null}]} - - {"id":"chatcmpl-123","object":"chat.completion.chunk","created":1694268190,"model":"gpt-3.5-turbo-0613", "system_fingerprint": "fp_44709d6fcb", "choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}]} + {"id":"chatcmpl-123","object":"chat.completion.chunk","created":1694268190,"model":"gpt-3.5-turbo-0125", "system_fingerprint": "fp_44709d6fcb", "choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}]} - title: Functions request: curl: | @@ -416,7 +410,7 @@ paths: "id": "chatcmpl-abc123", "object": "chat.completion", "created": 1699896916, - "model": "gpt-3.5-turbo-0613", + "model": "gpt-3.5-turbo-0125", "choices": [ { "index": 0, @@ -498,7 +492,7 @@ paths: "id": "chatcmpl-123", "object": "chat.completion", "created": 1702685778, - "model": "gpt-3.5-turbo-0613", + "model": "gpt-3.5-turbo-0125", "choices": [ { "index": 0, @@ -1201,47 +1195,174 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/CreateTranscriptionResponse" + oneOf: + - $ref: "#/components/schemas/CreateTranscriptionResponseJson" + - $ref: "#/components/schemas/CreateTranscriptionResponseVerboseJson" x-oaiMeta: name: Create transcription group: audio - returns: The transcribed text. + returns: The [transcription object](/docs/api-reference/audio/json-object) or a [verbose transcription object](/docs/api-reference/audio/verbose-json-object). examples: - request: - curl: | - curl https://api.openai.com/v1/audio/transcriptions \ - -H "Authorization: Bearer $OPENAI_API_KEY" \ - -H "Content-Type: multipart/form-data" \ - -F file="@/path/to/file/audio.mp3" \ - -F model="whisper-1" - python: | - from openai import OpenAI - client = OpenAI() + - title: Default + request: + curl: | + curl https://api.openai.com/v1/audio/transcriptions \ + -H "Authorization: Bearer $OPENAI_API_KEY" \ + -H "Content-Type: multipart/form-data" \ + -F file="@/path/to/file/audio.mp3" \ + -F model="whisper-1" + python: | + from openai import OpenAI + client = OpenAI() - audio_file = open("speech.mp3", "rb") - transcript = client.audio.transcriptions.create( - model="whisper-1", - file=audio_file - ) - node: | - import fs from "fs"; - import OpenAI from "openai"; + audio_file = open("speech.mp3", "rb") + transcript = client.audio.transcriptions.create( + model="whisper-1", + file=audio_file + ) + node: | + import fs from "fs"; + import OpenAI from "openai"; - const openai = new OpenAI(); + const openai = new OpenAI(); - async function main() { - const transcription = await openai.audio.transcriptions.create({ - file: fs.createReadStream("audio.mp3"), - model: "whisper-1", - }); + async function main() { + const transcription = await openai.audio.transcriptions.create({ + file: fs.createReadStream("audio.mp3"), + model: "whisper-1", + }); - console.log(transcription.text); + console.log(transcription.text); + } + main(); + response: &basic_transcription_response_example | + { + "text": "Imagine the wildest idea that you've ever had, and you're curious about how it might scale to something that's a 100, a 1,000 times bigger. This is a place where you can get to do that." + } + - title: Word timestamps + request: + curl: | + curl https://api.openai.com/v1/audio/transcriptions \ + -H "Authorization: Bearer $OPENAI_API_KEY" \ + -H "Content-Type: multipart/form-data" \ + -F file="@/path/to/file/audio.mp3" \ + -F "timestamp_granularities[]=word" \ + -F model="whisper-1" \ + -F response_format="verbose_json" + python: | + from openai import OpenAI + client = OpenAI() + + audio_file = open("speech.mp3", "rb") + transcript = client.audio.transcriptions.create( + file=audio_file, + model="whisper-1", + response_format="verbose_json", + timestamp_granularities=["word"] + ) + + print(transcript.words) + node: | + import fs from "fs"; + import OpenAI from "openai"; + + const openai = new OpenAI(); + + async function main() { + const transcription = await openai.audio.transcriptions.create({ + file: fs.createReadStream("audio.mp3"), + model: "whisper-1", + response_format: "verbose_json", + timestamp_granularities: ["word"] + }); + + console.log(transcription.text); + } + main(); + response: | + { + "task": "transcribe", + "language": "english", + "duration": 8.470000267028809, + "text": "The beach was a popular spot on a hot summer day. People were swimming in the ocean, building sandcastles, and playing beach volleyball.", + "words": [ + { + "word": "The", + "start": 0.0, + "end": 0.23999999463558197 + }, + ... + { + "word": "volleyball", + "start": 7.400000095367432, + "end": 7.900000095367432 + } + ] + } + - title: Segment timestamps + request: + curl: | + curl https://api.openai.com/v1/audio/transcriptions \ + -H "Authorization: Bearer $OPENAI_API_KEY" \ + -H "Content-Type: multipart/form-data" \ + -F file="@/path/to/file/audio.mp3" \ + -F "timestamp_granularities[]=segment" \ + -F model="whisper-1" \ + -F response_format="verbose_json" + python: | + from openai import OpenAI + client = OpenAI() + + audio_file = open("speech.mp3", "rb") + transcript = client.audio.transcriptions.create( + file=audio_file, + model="whisper-1", + response_format="verbose_json", + timestamp_granularities=["segment"] + ) + + print(transcript.words) + node: | + import fs from "fs"; + import OpenAI from "openai"; + + const openai = new OpenAI(); + + async function main() { + const transcription = await openai.audio.transcriptions.create({ + file: fs.createReadStream("audio.mp3"), + model: "whisper-1", + response_format: "verbose_json", + timestamp_granularities: ["segment"] + }); + + console.log(transcription.text); + } + main(); + response: &verbose_transcription_response_example | + { + "task": "transcribe", + "language": "english", + "duration": 8.470000267028809, + "text": "The beach was a popular spot on a hot summer day. People were swimming in the ocean, building sandcastles, and playing beach volleyball.", + "segments": [ + { + "id": 0, + "seek": 0, + "start": 0.0, + "end": 3.319999933242798, + "text": " The beach was a popular spot on a hot summer day.", + "tokens": [ + 50364, 440, 7534, 390, 257, 3743, 4008, 322, 257, 2368, 4266, 786, 13, 50530 + ], + "temperature": 0.0, + "avg_logprob": -0.2860786020755768, + "compression_ratio": 1.2363636493682861, + "no_speech_prob": 0.00985979475080967 + }, + ... + ] } - main(); - response: | - { - "text": "Imagine the wildest idea that you've ever had, and you're curious about how it might scale to something that's a 100, a 1,000 times bigger. This is a place where you can get to do that." - } /audio/translations: post: operationId: createTranslation @@ -1260,7 +1381,9 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/CreateTranslationResponse" + oneOf: + - $ref: "#/components/schemas/CreateTranslationResponseJson" + - $ref: "#/components/schemas/CreateTranslationResponseVerboseJson" x-oaiMeta: name: Create translation group: audio @@ -1658,7 +1781,7 @@ paths: { "object": "fine_tuning.job", "id": "ftjob-abc123", - "model": "gpt-3.5-turbo-0613", + "model": "gpt-3.5-turbo-0125", "created_at": 1614807352, "fine_tuned_model": null, "organization_id": "org-123", @@ -1711,7 +1834,7 @@ paths: { "object": "fine_tuning.job", "id": "ftjob-abc123", - "model": "gpt-3.5-turbo-0613", + "model": "gpt-3.5-turbo-0125", "created_at": 1614807352, "fine_tuned_model": null, "organization_id": "org-123", @@ -1760,7 +1883,7 @@ paths: { "object": "fine_tuning.job", "id": "ftjob-abc123", - "model": "gpt-3.5-turbo-0613", + "model": "gpt-3.5-turbo-0125", "created_at": 1614807352, "fine_tuned_model": null, "organization_id": "org-123", @@ -2056,7 +2179,7 @@ paths: { "object": "fine_tuning.job", "id": "ftjob-abc123", - "model": "gpt-3.5-turbo-0613", + "model": "gpt-3.5-turbo-0125", "created_at": 1689376978, "fine_tuned_model": null, "organization_id": "org-123", @@ -2247,7 +2370,7 @@ paths: operationId: createModeration tags: - Moderations - summary: Classifies if text violates OpenAI's Content Policy + summary: Classifies if text is potentially harmful. requestBody: required: true content: @@ -2278,7 +2401,8 @@ paths: from openai import OpenAI client = OpenAI() - client.moderations.create(input="I want to kill them.") + moderation = client.moderations.create(input="I want to kill them.") + print(moderation) node.js: | import OpenAI from "openai"; @@ -5806,6 +5930,7 @@ components: "gpt-3.5-turbo-0301", "gpt-3.5-turbo-0613", "gpt-3.5-turbo-1106", + "gpt-3.5-turbo-0125", "gpt-3.5-turbo-16k-0613", ] x-oaiTypeLabel: string @@ -5833,10 +5958,10 @@ components: default: false nullable: true top_logprobs: - description: An integer between 0 and 5 specifying the number of most likely tokens to return at each token position, each with an associated log probability. `logprobs` must be set to `true` if this parameter is used. + description: An integer between 0 and 20 specifying the number of most likely tokens to return at each token position, each with an associated log probability. `logprobs` must be set to `true` if this parameter is used. type: integer minimum: 0 - maximum: 5 + maximum: 20 nullable: true max_tokens: description: | @@ -5863,7 +5988,7 @@ components: response_format: type: object description: | - An object specifying the format that the model must output. Compatible with [GPT-4 Turbo](/docs/models/gpt-4-and-gpt-4-turbo) and `gpt-3.5-turbo-1106`. + An object specifying the format that the model must output. Compatible with [GPT-4 Turbo](/docs/models/gpt-4-and-gpt-4-turbo) and all GPT-3.5 Turbo models newer than `gpt-3.5-turbo-1106`. Setting to `{ "type": "json_object" }` enables JSON mode, which guarantees the message the model generates is valid JSON. @@ -6111,7 +6236,7 @@ components: description: The token. type: string logprob: &chat_completion_response_logprobs_token_logprob - description: The log probability of this token. + description: The log probability of this token, if it is within the top 20 most likely tokens. Otherwise, the value `-9999.0` is used to signify that the token is very unlikely. type: number bytes: &chat_completion_response_logprobs_bytes description: A list of integers representing the UTF-8 bytes representation of the token. Useful in instances where characters are represented by multiple tokens and their byte representations must be combined to generate the correct text representation. Can be `null` if there is no bytes representation for the token. @@ -6261,7 +6386,7 @@ components: default: "url" example: "url" nullable: true - description: The format in which the generated images are returned. Must be one of `url` or `b64_json`. + description: The format in which the generated images are returned. Must be one of `url` or `b64_json`. URLs are only valid for 60 minutes after the image has been generated. size: &images_size type: string enum: ["256x256", "512x512", "1024x1024", "1792x1024", "1024x1792"] @@ -6415,7 +6540,7 @@ components: CreateModerationResponse: type: object - description: Represents policy compliance report by OpenAI's content moderation model against a given input. + description: Represents if a given text input is potentially harmful. properties: id: type: string @@ -6431,7 +6556,7 @@ components: properties: flagged: type: boolean - description: Whether the content violates [OpenAI's usage policies](/policies/usage-policies). + description: Whether any of the below categories are flagged. categories: type: object description: A list of the categories, and whether they are flagged or not. @@ -6808,7 +6933,7 @@ components: format: binary model: description: | - ID of the model to use. Only `whisper-1` is currently available. + ID of the model to use. Only `whisper-1` (which is powered by our open source Whisper V2 model) is currently available. example: whisper-1 anyOf: - type: string @@ -6839,18 +6964,132 @@ components: The sampling temperature, between 0 and 1. Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic. If set to 0, the model will use [log probability](https://en.wikipedia.org/wiki/Log_probability) to automatically increase the temperature until certain thresholds are hit. type: number default: 0 + timestamp_granularities[]: + description: | + The timestamp granularities to populate for this transcription. `response_format` must be set `verbose_json` to use timestamp granularities. Either or both of these options are supported: `word`, or `segment`. Note: There is no additional latency for segment timestamps, but generating word timestamps incurs additional latency. + type: array + items: + type: string + enum: + - word + - segment + default: [segment] required: - file - model # Note: This does not currently support the non-default response format types. - CreateTranscriptionResponse: + CreateTranscriptionResponseJson: type: object + description: Represents a transcription response returned by model, based on the provided input. properties: text: type: string + description: The transcribed text. + required: + - text + x-oaiMeta: + name: The transcription object + group: audio + example: *basic_transcription_response_example + + TranscriptionSegment: + type: object + properties: + id: + type: integer + description: Unique identifier of the segment. + seek: + type: integer + description: Seek offset of the segment. + start: + type: number + format: float + description: Start time of the segment in seconds. + end: + type: number + format: float + description: End time of the segment in seconds. + text: + type: string + description: Text content of the segment. + tokens: + type: array + items: + type: integer + description: Array of token IDs for the text content. + temperature: + type: number + format: float + description: Temperature parameter used for generating the segment. + avg_logprob: + type: number + format: float + description: Average logprob of the segment. If the value is lower than -1, consider the logprobs failed. + compression_ratio: + type: number + format: float + description: Compression ratio of the segment. If the value is greater than 2.4, consider the compression failed. + no_speech_prob: + type: number + format: float + description: Probability of no speech in the segment. If the value is higher than 1.0 and the `avg_logprob` is below -1, consider this segment silent. required: + - id + - seek + - start + - end - text + - tokens + - temperature + - avg_logprob + - compression_ratio + - no_speech_prob + + TranscriptionWord: + type: object + properties: + word: + type: string + description: The text content of the word. + start: + type: number + format: float + description: Start time of the word in seconds. + end: + type: number + format: float + description: End time of the word in seconds. + required: [word, start, end] + + CreateTranscriptionResponseVerboseJson: + type: object + description: Represents a verbose json transcription response returned by model, based on the provided input. + properties: + language: + type: string + description: The language of the input audio. + duration: + type: string + description: The duration of the input audio. + text: + type: string + description: The transcribed text. + words: + type: array + description: Extracted words and their corresponding timestamps. + items: + $ref: '#/components/schemas/TranscriptionWord' + segments: + type: array + description: Segments of the transcribed text and their corresponding details. + items: + $ref: '#/components/schemas/TranscriptionSegment' + required: [language, duration, text] + x-oaiMeta: + name: The transcription object + group: audio + example: *verbose_transcription_response_example CreateTranslationRequest: type: object @@ -6864,7 +7103,7 @@ components: format: binary model: description: | - ID of the model to use. Only `whisper-1` is currently available. + ID of the model to use. Only `whisper-1` (which is powered by our open source Whisper V2 model) is currently available. example: whisper-1 anyOf: - type: string @@ -6890,7 +7129,7 @@ components: - model # Note: This does not currently support the non-default response format types. - CreateTranslationResponse: + CreateTranslationResponseJson: type: object properties: text: @@ -6898,6 +7137,25 @@ components: required: - text + CreateTranslationResponseVerboseJson: + type: object + properties: + language: + type: string + description: The language of the output translation (always `english`). + duration: + type: string + description: The duration of the input audio. + text: + type: string + description: The translated text. + segments: + type: array + description: Segments of the translated text and their corresponding details. + items: + $ref: '#/components/schemas/TranscriptionSegment' + required: [language, duration, text] + CreateSpeechRequest: type: object additionalProperties: false @@ -6919,10 +7177,10 @@ components: type: string enum: ["alloy", "echo", "fable", "onyx", "nova", "shimmer"] response_format: - description: "The format to audio in. Supported formats are `mp3`, `opus`, `aac`, and `flac`." + description: "The format to audio in. Supported formats are `mp3`, `opus`, `aac`, `flac`, `wav`, and `pcm`." default: "mp3" type: string - enum: ["mp3", "opus", "aac", "flac"] + enum: ["mp3", "opus", "aac", "flac", "wav", "pcm"] speed: description: "The speed of the generated audio. Select a value from `0.25` to `4.0`. `1.0` is the default." type: number @@ -7579,8 +7837,8 @@ components: properties: code: type: string - description: One of `server_error` or `rate_limit_exceeded`. - enum: ["server_error", "rate_limit_exceeded"] + description: One of `server_error`, `rate_limit_exceeded`, or `invalid_prompt`. + enum: ["server_error", "rate_limit_exceeded", "invalid_prompt"] message: type: string description: A human-readable description of the error. @@ -8710,6 +8968,12 @@ x-oaiMeta: - type: endpoint key: createTranslation path: createTranslation + - type: object + key: CreateTranscriptionResponseJson + path: json-object + - type: object + key: CreateTranscriptionResponseVerboseJson + path: verbose-json-object - id: chat title: Chat description: | @@ -8829,7 +9093,7 @@ x-oaiMeta: - id: moderations title: Moderations description: | - Given a input text, outputs if the model classifies it as violating OpenAI's content policy. + Given some input text, outputs if the model classifies it as potentially harmful across several categories. Related guide: [Moderations](/docs/guides/moderation) sections: @@ -8980,7 +9244,7 @@ x-oaiMeta: title: Completions legacy: true description: | - Given a prompt, the model will return one or more predicted completions along with the probabilities of alternative tokens at each position. Most developer should use our [Chat Completions API](/docs/guides/text-generation/text-generation-models) to leverage our best and newest models. Most models that support the legacy Completions endpoint [will be shut off on January 4th, 2024](/docs/deprecations/2023-07-06-gpt-and-embeddings). + Given a prompt, the model will return one or more predicted completions along with the probabilities of alternative tokens at each position. Most developer should use our [Chat Completions API](/docs/guides/text-generation/text-generation-models) to leverage our best and newest models. sections: - type: endpoint key: createCompletion diff --git a/runs/models.tsp b/runs/models.tsp index 15ab33b54..eff9b0e15 100644 --- a/runs/models.tsp +++ b/runs/models.tsp @@ -357,8 +357,8 @@ model RunObject { /** The last error associated with this run. Will be `null` if there are no errors. */ last_error: { - /** One of `server_error` or `rate_limit_exceeded`. */ - code: "server_error" | "rate_limit_exceeded"; + /** One of `server_error`, `rate_limit_exceeded`, or `invalid_prompt`. */ + code: "server_error" | "rate_limit_exceeded" | "invalid_prompt"; /** A human-readable description of the error. */ message: string; diff --git a/tsp-output/@typespec/openapi3/openapi.yaml b/tsp-output/@typespec/openapi3/openapi.yaml index ade9e3fd7..83319a41b 100644 --- a/tsp-output/@typespec/openapi3/openapi.yaml +++ b/tsp-output/@typespec/openapi3/openapi.yaml @@ -378,7 +378,9 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/CreateTranscriptionResponse' + anyOf: + - $ref: '#/components/schemas/CreateTranscriptionResponseJson' + - $ref: '#/components/schemas/CreateTranscriptionResponseVerboseJson' text/plain: schema: type: string @@ -407,7 +409,9 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/CreateTranslationResponse' + anyOf: + - $ref: '#/components/schemas/CreateTranslationResponseJson' + - $ref: '#/components/schemas/CreateTranslationResponseVerboseJson' text/plain: schema: type: string @@ -954,7 +958,7 @@ paths: tags: - Moderations operationId: createModeration - summary: Classifies if text violates OpenAI's Content Policy + summary: Classifies if text is potentially harmful. parameters: [] responses: '200': @@ -1857,65 +1861,6 @@ components: enum: - retrieval description: 'The type of tool being defined: `retrieval`' - AudioSegment: - type: object - required: - - id - - seek - - start - - end - - text - - tokens - - temperature - - avg_logprob - - compression_ratio - - no_speech_prob - properties: - id: - type: integer - format: int64 - description: The zero-based index of this segment. - seek: - type: integer - format: int64 - description: |- - The seek position associated with the processing of this audio segment. Seek positions are - expressed as hundredths of seconds. The model may process several segments from a single seek - position, so while the seek position will never represent a later time than the segment's - start, the segment's start may represent a significantly later time than the segment's - associated seek position. - start: - type: number - format: double - description: The time at which this segment started relative to the beginning of the audio. - end: - type: number - format: double - description: The time at which this segment ended relative to the beginning of the audio. - text: - type: string - description: The text that was part of this audio segment. - tokens: - $ref: '#/components/schemas/TokenArrayItem' - description: The token IDs matching the text in this audio segment. - temperature: - type: number - format: double - minimum: 0 - maximum: 1 - description: The temperature score associated with this audio segment. - avg_logprob: - type: number - format: double - description: The average log probability associated with this audio segment. - compression_ratio: - type: number - format: double - description: The compression ratio of this audio segment. - no_speech_prob: - type: number - format: double - description: The probability of no speech detection within this audio segment. BatchSize: type: integer format: int64 @@ -2244,7 +2189,9 @@ components: logprob: type: number format: double - description: The log probability of this token. + description: |- + The log probability of this token, if it is within the top 20 most likely tokens. Otherwise, + the value `-9999.0` is used to signify that the token is very unlikely. bytes: type: array items: @@ -2444,6 +2391,7 @@ components: - gpt-3.5-turbo-0301 - gpt-3.5-turbo-0613 - gpt-3.5-turbo-1106 + - gpt-3.5-turbo-0125 - gpt-3.5-turbo-16k-0613 description: |- ID of the model to use. See the [model endpoint compatibility](/docs/models/model-endpoint-compatibility) @@ -2491,9 +2439,9 @@ components: format: int64 nullable: true minimum: 0 - maximum: 5 + maximum: 20 description: |- - An integer between 0 and 5 specifying the number of most likely tokens to return at each token + An integer between 0 and 20 specifying the number of most likely tokens to return at each token position, each with an associated log probability. `logprobs` must be set to `true` if this parameter is used. max_tokens: @@ -2543,7 +2491,8 @@ components: default: text description: |- An object specifying the format that the model must output. Compatible with - [GPT-4 Turbo](/docs/models/gpt-4-and-gpt-4-turbo) and `gpt-3.5-turbo-1106`. + [GPT-4 Turbo](/docs/models/gpt-4-and-gpt-4-turbo) and all GPT-3.5 Turbo models newer than + `gpt-3.5-turbo-1106`. Setting to `{ "type": "json_object" }` enables JSON mode, which guarantees the message the model generates is valid JSON. @@ -3340,7 +3289,9 @@ components: - url - b64_json nullable: true - description: The format in which the generated images are returned. Must be one of `url` or `b64_json`. + description: |- + The format in which the generated images are returned. Must be one of `url` or `b64_json`. URLs + are only valid for 60 minutes after the image has been generated. default: url size: type: string @@ -3512,7 +3463,7 @@ components: properties: flagged: type: boolean - description: Whether the content violates [OpenAI's usage policies](/policies/usage-policies). + description: Whether any of the below categories are flagged. categories: type: object properties: @@ -3641,7 +3592,7 @@ components: - categories - category_scores description: A list of moderation objects. - description: Represents policy compliance report by OpenAI's content moderation model against a given input. + description: Represents if a given text input is potentially harmful. CreateRunRequest: type: object required: @@ -3743,7 +3694,9 @@ components: - opus - aac - flac - description: The format to audio in. Supported formats are `mp3`, `opus`, `aac`, and `flac`. + - wav + - pcm + description: The format to audio in. Supported formats are `mp3`, `opus`, `aac`, `flac`, `wav`, and `pcm`. default: mp3 speed: type: number @@ -3836,7 +3789,9 @@ components: - type: string enum: - whisper-1 - description: ID of the model to use. Only `whisper-1` is currently available. + description: |- + ID of the model to use. Only `whisper-1` (which is powered by our open source Whisper V2 model) + is currently available. x-oaiTypeLabel: string language: type: string @@ -3876,33 +3831,63 @@ components: the model will use [log probability](https://en.wikipedia.org/wiki/Log_probability) to automatically increase the temperature until certain thresholds are hit. default: 0 - CreateTranscriptionResponse: + timestamp_granularities: + type: array + items: + type: string + enum: + - word + - segment + description: |- + The timestamp granularities to populate for this transcription. `response_format` must be set + `verbose_json` to use timestamp granularities. Either or both of these options are supported: + `word`, or `segment`. Note: There is no additional latency for segment timestamps, but + generating word timestamps incurs additional latency. + default: + - segment + CreateTranscriptionResponseJson: type: object required: - text properties: text: type: string - description: The transcribed text for the provided audio data. + description: The transcribed text. + description: Represents a transcription response returned by model, based on the provided input. + CreateTranscriptionResponseVerboseJson: + type: object + required: + - task + - language + - duration + - text + properties: task: type: string enum: - transcribe - description: The label that describes which operation type generated the accompanying response data. + description: The task label. language: type: string - description: The spoken language that was detected in the audio data. + description: The language of the input audio. duration: type: number format: double - description: The total duration of the audio processed to produce accompanying transcription information. + description: The duration of the input audio. + text: + type: string + description: The transcribed text. + words: + type: array + items: + $ref: '#/components/schemas/TranscriptionWord' + description: Extracted words and their corresponding timestamps. segments: type: array items: - $ref: '#/components/schemas/AudioSegment' - description: |- - A collection of information about the timing, probabilities, and other detail of each processed - audio segment. + $ref: '#/components/schemas/TranscriptionSegment' + description: Segments of the transcribed text and their corresponding details. + description: Represents a verbose json transcription response returned by model, based on the provided input. CreateTranslationRequestMultiPart: type: object required: @@ -3922,7 +3907,9 @@ components: - type: string enum: - whisper-1 - description: ID of the model to use. Only `whisper-1` is currently available. + description: |- + ID of the model to use. Only `whisper-1` (which is powered by our open source Whisper V2 model) + is currently available. x-oaiTypeLabel: string prompt: type: string @@ -3956,33 +3943,41 @@ components: the model will use [log probability](https://en.wikipedia.org/wiki/Log_probability) to automatically increase the temperature until certain thresholds are hit. default: 0 - CreateTranslationResponse: + CreateTranslationResponseJson: type: object required: - text properties: text: type: string - description: The translated text for the provided audio data. + CreateTranslationResponseVerboseJson: + type: object + required: + - task + - language + - duration + - text + properties: task: type: string enum: - translate - description: The label that describes which operation type generated the accompanying response data. + description: The task label. language: type: string - description: The spoken language that was detected in the audio data. + description: The language of the output translation (always `english`). duration: type: number format: double - description: The total duration of the audio processed to produce accompanying translation information. + description: The duration of the input audio. + text: + type: string + description: The translated text. segments: type: array items: - $ref: '#/components/schemas/AudioSegment' - description: |- - A collection of information about the timing, probabilities, and other detail of each processed - audio segment. + $ref: '#/components/schemas/TranscriptionSegment' + description: Segments of the translated text and their corresponding details. DeleteAssistantFileResponse: type: object required: @@ -5108,7 +5103,8 @@ components: enum: - server_error - rate_limit_exceeded - description: One of `server_error` or `rate_limit_exceeded`. + - invalid_prompt + description: One of `server_error`, `rate_limit_exceeded`, or `invalid_prompt`. message: type: string description: A human-readable description of the error. @@ -5608,6 +5604,82 @@ components: type: integer format: int64 minItems: 1 + TranscriptionSegment: + type: object + required: + - id + - seek + - start + - end + - text + - tokens + - temperature + - avg_logprob + - compression_ratio + - no_speech_prob + properties: + id: + type: integer + format: int64 + description: Unique identifier of the segment. + seek: + type: integer + format: int64 + description: Seek offset of the segment. + start: + type: number + format: double + description: Start time of the segment in seconds. + end: + type: number + format: double + description: End time of the segment in seconds. + text: + type: string + description: Text content of the segment. + tokens: + $ref: '#/components/schemas/TokenArrayItem' + description: Array of token IDs for the text content. + temperature: + type: number + format: double + minimum: 0 + maximum: 1 + description: Temperature parameter used for generating the segment. + avg_logprob: + type: number + format: double + description: Average logprob of the segment. If the value is lower than -1, consider the logprobs failed. + compression_ratio: + type: number + format: double + description: |- + Compression ratio of the segment. If the value is greater than 2.4, consider the compression + failed. + no_speech_prob: + type: number + format: double + description: |- + Probability of no speech in the segment. If the value is higher than 1.0 and the `avg_logprob` + is below -1, consider this segment silent. + TranscriptionWord: + type: object + required: + - word + - start + - end + properties: + word: + type: string + description: The text content of the word. + start: + type: number + format: double + description: Start time of the word in seconds. + end: + type: number + format: double + description: End time of the word in seconds. User: type: string securitySchemes: