diff --git a/.dotnet/CHANGELOG.md b/.dotnet/CHANGELOG.md index 47879fd8b..1d88ffe18 100644 --- a/.dotnet/CHANGELOG.md +++ b/.dotnet/CHANGELOG.md @@ -6,11 +6,9 @@ - Chat completion now supports audio input and output! - To configure a chat completion to request audio output using the `gpt-4o-audio-preview` model, create a `ChatAudioOptions` instance and provide it on `ChatCompletionOptions.AudioOptions`. - - Audio is always represented as a `ChatMessageContentPart`: - - User audio input can be instantiated via `ChatMessageContentPart.CreateAudioPart(BinaryData, ChatAudioInputFormat)` and will populate the `AudioBytes` and `AudioInputFormat` properties on `ChatMessageContentPart` - - Response audio associated with the items in `Content` of a `ChatCompletion` or `ContentUpdate` of a `StreamingChatCompletionUpdate` will populate the `AudioBytes`, `AudioTranscript`, `AudioExpiresAt`, and `AudioCorrelationId` properties - - Audio referring to a previous response's output can be created via `ChatMessageContentPart.CreateAudioPart(string)` and will populate the `AudioCorrelationId` property. - - The `AssistantChatMessage(IEnumerable)` and `AssistantChatMessage(ChatCompletion)` constructors will automatically infer `AudioCorrelationId`, simplifying conversation history management + - Input chat audio is provided to `UserChatMessage` instances using `ChatContentPart.CreateInputAudioPart()` + - Output chat audio is provided on the `ResponseAudio` property of `ChatCompletion` + - References to prior assistant audio are provided via `ResponseAudioReference` instances on the `AudioReference` property of `AssistantChatMessage`; `AssistantChatMessage(chatCompletion)` will automatically handle this, too - For more information, see the example in the README ## 2.1.0 (2024-12-04) diff --git a/.dotnet/README.md b/.dotnet/README.md index d0d710937..122070d2c 100644 --- a/.dotnet/README.md +++ b/.dotnet/README.md @@ -389,28 +389,23 @@ ChatCompletion completion = client.CompleteChat(messages, options); void PrintAudioContent() { - foreach (ChatMessageContentPart contentPart in completion.Content) + if (completion.ResponseAudio is ChatResponseAudio responseAudio) { - if (contentPart.AudioCorrelationId is not null) + Console.WriteLine($"Response audio transcript: {responseAudio.Transcript}"); + string outputFilePath = $"{responseAudio.Id}.mp3"; + using (FileStream outputFileStream = File.OpenWrite(outputFilePath)) { - Console.WriteLine($"Response audio transcript: {contentPart.AudioTranscript}"); - - string outputFilePath = $"{contentPart.AudioCorrelationId}.mp3"; - using (FileStream outputFileStream = File.OpenWrite(outputFilePath)) - { - outputFileStream.Write(contentPart.AudioBytes); - } - Console.WriteLine($"Response audio written to file: {outputFilePath}"); - Console.WriteLine($"Valid on followup requests until: {contentPart.AudioExpiresAt}"); + outputFileStream.Write(responseAudio.Data); } + Console.WriteLine($"Response audio written to file: {outputFilePath}"); + Console.WriteLine($"Valid on followup requests until: {responseAudio.ExpiresAt}"); } } PrintAudioContent(); -// To refer to past audio output, create an assistant message from the earlier ChatCompletion, use the earlier -// response content part, or use ChatMessageContentPart.CreateAudioPart(string) to manually instantiate a part. - +// To refer to past audio output, create an assistant message from the earlier ChatCompletion or instantiate a +// ChatResponseAudioReference(string) from the .Id of the completion's .ResponseAudio property. messages.Add(new AssistantChatMessage(completion)); messages.Add("Can you say that like a pirate?"); diff --git a/.dotnet/api/OpenAI.netstandard2.0.cs b/.dotnet/api/OpenAI.netstandard2.0.cs index c57f4943a..8ed7eb820 100644 --- a/.dotnet/api/OpenAI.netstandard2.0.cs +++ b/.dotnet/api/OpenAI.netstandard2.0.cs @@ -1131,6 +1131,7 @@ public class AssistantChatMessage : ChatMessage, IJsonModel parameter instead.")] public AssistantChatMessage(ChatFunctionCall functionCall); public AssistantChatMessage(params ChatMessageContentPart[] contentParts); + public AssistantChatMessage(ChatResponseAudioReference responseAudioReference); public AssistantChatMessage(IEnumerable contentParts); public AssistantChatMessage(IEnumerable toolCalls); public AssistantChatMessage(string content); @@ -1138,6 +1139,7 @@ public class AssistantChatMessage : ChatMessage, IJsonModel ToolCalls { get; } protected override ChatMessage JsonModelCreateCore(ref Utf8JsonReader reader, ModelReaderWriterOptions options); protected override void JsonModelWriteCore(Utf8JsonWriter writer, ModelReaderWriterOptions options); @@ -1184,6 +1186,7 @@ public class ChatCompletion : IJsonModel, IPersistableModel RefusalTokenLogProbabilities { get; } + public ChatResponseAudio ResponseAudio { get; } public ChatMessageRole Role { get; } public string SystemFingerprint { get; } public IReadOnlyList ToolCalls { get; } @@ -1289,6 +1292,7 @@ public class ChatMessage : IJsonModel, IPersistableModel contentParts); public static AssistantChatMessage CreateAssistantMessage(IEnumerable toolCalls); public static AssistantChatMessage CreateAssistantMessage(string content); @@ -1315,10 +1319,7 @@ public class ChatMessageContent : ObjectModel.Collection } public class ChatMessageContentPart : IJsonModel, IPersistableModel { public BinaryData AudioBytes { get; } - public string AudioCorrelationId { get; } - public DateTimeOffset? AudioExpiresAt { get; } public ChatInputAudioFormat? AudioInputFormat { get; } - public string AudioTranscript { get; } public BinaryData ImageBytes { get; } public string ImageBytesMediaType { get; } public ChatImageDetailLevel? ImageDetailLevel { get; } @@ -1326,10 +1327,9 @@ public class ChatMessageContentPart : IJsonModel, IPersi public ChatMessageContentPartKind Kind { get; } public string Refusal { get; } public string Text { get; } - public static ChatMessageContentPart CreateAudioPart(BinaryData audioBytes, ChatInputAudioFormat audioFormat); - public static ChatMessageContentPart CreateAudioPart(string audioCorrelationId); public static ChatMessageContentPart CreateImagePart(BinaryData imageBytes, string imageBytesMediaType, ChatImageDetailLevel? imageDetailLevel = null); public static ChatMessageContentPart CreateImagePart(Uri imageUri, ChatImageDetailLevel? imageDetailLevel = null); + public static ChatMessageContentPart CreateInputAudioPart(BinaryData audioBytes, ChatInputAudioFormat audioFormat); public static ChatMessageContentPart CreateRefusalPart(string refusal); public static ChatMessageContentPart CreateTextPart(string text); public static explicit operator ChatMessageContentPart(ClientResult result); @@ -1372,6 +1372,20 @@ public class ChatOutputTokenUsageDetails : IJsonModel, IPersistableModel { + public BinaryData Data { get; } + public DateTimeOffset ExpiresAt { get; } + public string Id { get; } + public string Transcript { get; } + public static explicit operator ChatResponseAudio(ClientResult result); + public static implicit operator BinaryContent(ChatResponseAudio chatResponseAudio); + } + public class ChatResponseAudioReference : IJsonModel, IPersistableModel { + public ChatResponseAudioReference(string id); + public string Id { get; } + public static explicit operator ChatResponseAudioReference(ClientResult result); + public static implicit operator BinaryContent(ChatResponseAudioReference chatResponseAudioReference); + } public class ChatResponseFormat : IJsonModel, IPersistableModel { public static ChatResponseFormat CreateJsonObjectFormat(); public static ChatResponseFormat CreateJsonSchemaFormat(string jsonSchemaFormatName, BinaryData jsonSchema, string jsonSchemaFormatDescription = null, bool? jsonSchemaIsStrict = null); @@ -1466,13 +1480,14 @@ public class FunctionChatMessage : ChatMessage, IJsonModel, protected override BinaryData PersistableModelWriteCore(ModelReaderWriterOptions options); } public static class OpenAIChatModelFactory { - public static ChatCompletion ChatCompletion(string id = null, ChatFinishReason finishReason = ChatFinishReason.Stop, ChatMessageContent content = null, string refusal = null, IEnumerable toolCalls = null, ChatMessageRole role = ChatMessageRole.System, ChatFunctionCall functionCall = null, IEnumerable contentTokenLogProbabilities = null, IEnumerable refusalTokenLogProbabilities = null, DateTimeOffset createdAt = default, string model = null, string systemFingerprint = null, ChatTokenUsage usage = null, BinaryData audioBytes = null, string audioCorrelationId = null, string audioTranscript = null, DateTimeOffset? audioExpiresAt = null); + public static ChatCompletion ChatCompletion(string id = null, ChatFinishReason finishReason = ChatFinishReason.Stop, ChatMessageContent content = null, string refusal = null, IEnumerable toolCalls = null, ChatMessageRole role = ChatMessageRole.System, ChatFunctionCall functionCall = null, IEnumerable contentTokenLogProbabilities = null, IEnumerable refusalTokenLogProbabilities = null, DateTimeOffset createdAt = default, string model = null, string systemFingerprint = null, ChatTokenUsage usage = null, ChatResponseAudio responseAudio = null); public static ChatInputTokenUsageDetails ChatInputTokenUsageDetails(int audioTokenCount = 0, int cachedTokenCount = 0); public static ChatOutputTokenUsageDetails ChatOutputTokenUsageDetails(int reasoningTokenCount = 0, int audioTokenCount = 0); + public static ChatResponseAudio ChatResponseAudio(BinaryData data, string id = null, string transcript = null, DateTimeOffset expiresAt = default); public static ChatTokenLogProbabilityDetails ChatTokenLogProbabilityDetails(string token = null, float logProbability = 0, ReadOnlyMemory? utf8Bytes = null, IEnumerable topLogProbabilities = null); public static ChatTokenTopLogProbabilityDetails ChatTokenTopLogProbabilityDetails(string token = null, float logProbability = 0, ReadOnlyMemory? utf8Bytes = null); public static ChatTokenUsage ChatTokenUsage(int outputTokenCount = 0, int inputTokenCount = 0, int totalTokenCount = 0, ChatOutputTokenUsageDetails outputTokenDetails = null, ChatInputTokenUsageDetails inputTokenDetails = null); - public static StreamingChatCompletionUpdate StreamingChatCompletionUpdate(string completionId = null, ChatMessageContent contentUpdate = null, StreamingChatFunctionCallUpdate functionCallUpdate = null, IEnumerable toolCallUpdates = null, ChatMessageRole? role = null, string refusalUpdate = null, IEnumerable contentTokenLogProbabilities = null, IEnumerable refusalTokenLogProbabilities = null, ChatFinishReason? finishReason = null, DateTimeOffset createdAt = default, string model = null, string systemFingerprint = null, ChatTokenUsage usage = null, string audioCorrelationId = null, string audioTranscript = null, BinaryData audioBytes = null, DateTimeOffset? audioExpiresAt = null); + public static StreamingChatCompletionUpdate StreamingChatCompletionUpdate(string completionId = null, ChatMessageContent contentUpdate = null, StreamingChatFunctionCallUpdate functionCallUpdate = null, IEnumerable toolCallUpdates = null, ChatMessageRole? role = null, string refusalUpdate = null, IEnumerable contentTokenLogProbabilities = null, IEnumerable refusalTokenLogProbabilities = null, ChatFinishReason? finishReason = null, DateTimeOffset createdAt = default, string model = null, string systemFingerprint = null, ChatTokenUsage usage = null, ChatResponseAudio responseAudio = null); [Obsolete("This class is obsolete. Please use StreamingChatToolCallUpdate instead.")] public static StreamingChatFunctionCallUpdate StreamingChatFunctionCallUpdate(string functionName = null, BinaryData functionArgumentsUpdate = null); public static StreamingChatToolCallUpdate StreamingChatToolCallUpdate(int index = 0, string toolCallId = null, ChatToolCallKind kind = ChatToolCallKind.Function, string functionName = null, BinaryData functionArgumentsUpdate = null); @@ -1488,6 +1503,7 @@ public class StreamingChatCompletionUpdate : IJsonModel RefusalTokenLogProbabilities { get; } public string RefusalUpdate { get; } + public ChatResponseAudio ResponseAudio { get; } public ChatMessageRole? Role { get; } public string SystemFingerprint { get; } public IReadOnlyList ToolCallUpdates { get; } diff --git a/.dotnet/examples/Chat/Example09_ChatWithAudio.cs b/.dotnet/examples/Chat/Example09_ChatWithAudio.cs index 758e538e2..848a47830 100644 --- a/.dotnet/examples/Chat/Example09_ChatWithAudio.cs +++ b/.dotnet/examples/Chat/Example09_ChatWithAudio.cs @@ -20,7 +20,7 @@ public void Example09_ChatWithAudio() BinaryData audioData = BinaryData.FromBytes(audioFileRawBytes); List messages = [ - new UserChatMessage(ChatMessageContentPart.CreateAudioPart(audioData, ChatInputAudioFormat.Wav)), + new UserChatMessage(ChatMessageContentPart.CreateInputAudioPart(audioData, ChatInputAudioFormat.Wav)), ]; // Output audio is requested by configuring AudioOptions on ChatCompletionOptions @@ -33,20 +33,16 @@ public void Example09_ChatWithAudio() void PrintAudioContent() { - foreach (ChatMessageContentPart contentPart in completion.Content) + if (completion.ResponseAudio is ChatResponseAudio responseAudio) { - if (contentPart.AudioCorrelationId is not null) + Console.WriteLine($"Response audio transcript: {responseAudio.Transcript}"); + string outputFilePath = $"{responseAudio.Id}.mp3"; + using (FileStream outputFileStream = File.OpenWrite(outputFilePath)) { - Console.WriteLine($"Response audio transcript: {contentPart.AudioTranscript}"); - - string outputFilePath = $"{contentPart.AudioCorrelationId}.mp3"; - using (FileStream outputFileStream = File.OpenWrite(outputFilePath)) - { - outputFileStream.Write(contentPart.AudioBytes); - } - Console.WriteLine($"Response audio written to file: {outputFilePath}"); - Console.WriteLine($"Valid on followup requests until: {contentPart.AudioExpiresAt}"); + outputFileStream.Write(responseAudio.Data); } + Console.WriteLine($"Response audio written to file: {outputFilePath}"); + Console.WriteLine($"Valid on followup requests until: {responseAudio.ExpiresAt}"); } } diff --git a/.dotnet/examples/Chat/Example10_ChatWithAudioAsync.cs b/.dotnet/examples/Chat/Example10_ChatWithAudioAsync.cs index 1a8f70748..f1df7a318 100644 --- a/.dotnet/examples/Chat/Example10_ChatWithAudioAsync.cs +++ b/.dotnet/examples/Chat/Example10_ChatWithAudioAsync.cs @@ -21,7 +21,7 @@ public async Task Example09_ChatWithAudioAsync() BinaryData audioData = BinaryData.FromBytes(audioFileRawBytes); List messages = [ - new UserChatMessage(ChatMessageContentPart.CreateAudioPart(audioData, ChatInputAudioFormat.Wav)), + new UserChatMessage(ChatMessageContentPart.CreateInputAudioPart(audioData, ChatInputAudioFormat.Wav)), ]; // Output audio is requested by configuring AudioOptions on ChatCompletionOptions @@ -34,20 +34,16 @@ public async Task Example09_ChatWithAudioAsync() async Task PrintAudioContentAsync() { - foreach (ChatMessageContentPart contentPart in completion.Content) + if (completion.ResponseAudio is ChatResponseAudio responseAudio) { - if (contentPart.AudioCorrelationId is not null) + Console.WriteLine($"Response audio transcript: {responseAudio.Transcript}"); + string outputFilePath = $"{responseAudio.Id}.mp3"; + using (FileStream outputFileStream = File.OpenWrite(outputFilePath)) { - Console.WriteLine($"Response audio transcript: {contentPart.AudioTranscript}"); - - string outputFilePath = $"{contentPart.AudioCorrelationId}.mp3"; - using (FileStream outputFileStream = File.OpenWrite(outputFilePath)) - { - await outputFileStream.WriteAsync(contentPart.AudioBytes); - } - Console.WriteLine($"Response audio written to file: {outputFilePath}"); - Console.WriteLine($"Valid on followup requests until: {contentPart.AudioExpiresAt}"); + await outputFileStream.WriteAsync(responseAudio.Data); } + Console.WriteLine($"Response audio written to file: {outputFilePath}"); + Console.WriteLine($"Valid on followup requests until: {responseAudio.ExpiresAt}"); } } diff --git a/.dotnet/src/Custom/Chat/AssistantChatMessage.Serialization.cs b/.dotnet/src/Custom/Chat/AssistantChatMessage.Serialization.cs index 13e493295..41fa66f7c 100644 --- a/.dotnet/src/Custom/Chat/AssistantChatMessage.Serialization.cs +++ b/.dotnet/src/Custom/Chat/AssistantChatMessage.Serialization.cs @@ -1,7 +1,4 @@ -using System; using System.ClientModel.Primitives; -using System.Collections.Generic; -using System.Linq; using System.Text.Json; namespace OpenAI.Chat; @@ -22,22 +19,9 @@ internal override void WriteCore(Utf8JsonWriter writer, ModelReaderWriterOptions writer.WriteStringValue(Role.ToSerialString()); // Content is optional, can be a single string or a collection of ChatMessageContentPart. - if (Optional.IsDefined(Content) && Content.IsInnerCollectionDefined() && Content.Count > 0) + if (Optional.IsDefined(Content) && Content.IsInnerCollectionDefined()) { - // AssistantChatMessage contrives a ChatMessageContent instance to expose the otherwise unrelated audio - // information as multimodal content. For serialization, we need to ensure the underlying, non-projected - // representation is populated. - if (Audio is null) - { - ChatMessageContentPart audioReferenceContentPart - = Content.FirstOrDefault(contentPart => !string.IsNullOrEmpty(contentPart.AudioCorrelationId)); - if (audioReferenceContentPart is not null) - { - Audio = new(audioReferenceContentPart.AudioCorrelationId); - } - } - - if (Content.Any(contentPart => !contentPart.IsContrived)) + if (Content.Count > 0) { writer.WritePropertyName("content"u8); if (Content.Count == 1 && Content[0].Text != null) @@ -49,10 +33,7 @@ ChatMessageContentPart audioReferenceContentPart writer.WriteStartArray(); foreach (ChatMessageContentPart part in Content) { - if (!part.IsContrived) - { - writer.WriteObjectValue(part, options); - } + writer.WriteObjectValue(part, options); } writer.WriteEndArray(); } @@ -63,105 +44,8 @@ ChatMessageContentPart audioReferenceContentPart writer.WriteOptionalProperty("name"u8, ParticipantName, options); writer.WriteOptionalCollection("tool_calls"u8, ToolCalls, options); writer.WriteOptionalProperty("function_call"u8, FunctionCall, options); - writer.WriteOptionalProperty("audio"u8, Audio, options); + writer.WriteOptionalProperty("audio"u8, ResponseAudioReference, options); writer.WriteSerializedAdditionalRawData(_additionalBinaryDataProperties, options); writer.WriteEndObject(); } - - internal static AssistantChatMessage DeserializeAssistantChatMessage(JsonElement element, ModelReaderWriterOptions options = null) - { - string refusal = default; - string name = default; - InternalChatCompletionRequestAssistantMessageAudio audio = default; - IList toolCalls = default; - ChatFunctionCall functionCall = default; - ChatMessageRole role = default; - ChatMessageContent content = default; - IDictionary serializedAdditionalRawData = default; - Dictionary rawDataDictionary = new Dictionary(); - foreach (var property in element.EnumerateObject()) - { - if (property.NameEquals("refusal"u8)) - { - if (property.Value.ValueKind == JsonValueKind.Null) - { - refusal = null; - continue; - } - refusal = property.Value.GetString(); - continue; - } - if (property.NameEquals("name"u8)) - { - name = property.Value.GetString(); - continue; - } - if (property.NameEquals("audio"u8)) - { - if (property.Value.ValueKind == JsonValueKind.Null) - { - audio = null; - continue; - } - audio = InternalChatCompletionRequestAssistantMessageAudio.DeserializeInternalChatCompletionRequestAssistantMessageAudio(property.Value, options); - continue; - } - if (property.NameEquals("tool_calls"u8)) - { - if (property.Value.ValueKind == JsonValueKind.Null) - { - continue; - } - List array = new List(); - foreach (var item in property.Value.EnumerateArray()) - { - array.Add(ChatToolCall.DeserializeChatToolCall(item, options)); - } - toolCalls = array; - continue; - } - if (property.NameEquals("function_call"u8)) - { - if (property.Value.ValueKind == JsonValueKind.Null) - { - functionCall = null; - continue; - } - functionCall = ChatFunctionCall.DeserializeChatFunctionCall(property.Value, options); - continue; - } - if (property.NameEquals("role"u8)) - { - role = property.Value.GetString().ToChatMessageRole(); - continue; - } - if (property.NameEquals("content"u8)) - { - DeserializeContentValue(property, ref content); - continue; - } - if (true) - { - rawDataDictionary ??= new Dictionary(); - rawDataDictionary.Add(property.Name, BinaryData.FromString(property.Value.GetRawText())); - } - } - serializedAdditionalRawData = rawDataDictionary; - // CUSTOM: Initialize Content collection property. - // If applicable, prepend a contrived content part representing an ID-based audio reference. - content ??= new(); - if (audio is not null) - { - content.Insert(0, new ChatMessageContentPart(ChatMessageContentPartKind.Audio, audioReference: new(audio.Id))); - } - return new AssistantChatMessage( - content ?? new ChatMessageContent(), - role, - serializedAdditionalRawData, - refusal, - name, - toolCalls ?? new ChangeTrackingList(), - functionCall, - audio); - } } diff --git a/.dotnet/src/Custom/Chat/AssistantChatMessage.cs b/.dotnet/src/Custom/Chat/AssistantChatMessage.cs index d1a03084c..261aaec9b 100644 --- a/.dotnet/src/Custom/Chat/AssistantChatMessage.cs +++ b/.dotnet/src/Custom/Chat/AssistantChatMessage.cs @@ -84,6 +84,18 @@ public AssistantChatMessage(ChatFunctionCall functionCall) FunctionCall = functionCall; } + /// + /// Creates a new instance of that represents a prior response from the model + /// that included audio with a correlation ID. + /// + /// The audio reference with an id, produced by the model. + public AssistantChatMessage(ChatResponseAudioReference responseAudioReference) + { + Argument.AssertNotNull(responseAudioReference, nameof(responseAudioReference)); + + ResponseAudioReference = responseAudioReference; + } + /// /// Creates a new instance of from a with /// an assistant role response. @@ -110,6 +122,10 @@ public AssistantChatMessage(ChatCompletion chatCompletion) Refusal = chatCompletion.Refusal; FunctionCall = chatCompletion.FunctionCall; + if (chatCompletion.ResponseAudio is not null) + { + ResponseAudioReference = new(chatCompletion.ResponseAudio.Id); + } foreach (ChatToolCall toolCall in chatCompletion.ToolCalls ?? []) { ToolCalls.Add(toolCall); @@ -134,5 +150,5 @@ public AssistantChatMessage(ChatCompletion chatCompletion) // CUSTOM: Made internal for reprojected representation within the content collection. [CodeGenMember("Audio")] - internal InternalChatCompletionRequestAssistantMessageAudio Audio { get; set; } + public ChatResponseAudioReference ResponseAudioReference { get; set; } } \ No newline at end of file diff --git a/.dotnet/src/Custom/Chat/ChatCompletion.cs b/.dotnet/src/Custom/Chat/ChatCompletion.cs index 594aa61fc..ad76ca8b6 100644 --- a/.dotnet/src/Custom/Chat/ChatCompletion.cs +++ b/.dotnet/src/Custom/Chat/ChatCompletion.cs @@ -71,8 +71,7 @@ public partial class ChatCompletion // CUSTOM: Flattened choice message property. /// The contents of the message. - public ChatMessageContent Content => _content ??= GetWrappedContent(); - private ChatMessageContent _content; + public ChatMessageContent Content => Choices[0].Message.Content; // CUSTOM: Flattened choice message property. /// The tool calls generated by the model, such as function calls. @@ -86,19 +85,6 @@ public partial class ChatCompletion [Obsolete($"This property is obsolete. Please use {nameof(ToolCalls)} instead.")] public ChatFunctionCall FunctionCall => Choices[0].Message.FunctionCall; - private ChatMessageContent GetWrappedContent() - { - if (Choices[0].Message.Audio is not null) - { - return new ChatMessageContent( - [ - new ChatMessageContentPart(ChatMessageContentPartKind.Audio, outputAudio: Choices[0].Message.Audio), - ..Choices[0].Message.Content, - ]); - } - else - { - return Choices[0].Message.Content; - } - } + /// The audio response generated by the model. + public ChatResponseAudio ResponseAudio => Choices[0].Message.Audio; } diff --git a/.dotnet/src/Custom/Chat/ChatMessage.cs b/.dotnet/src/Custom/Chat/ChatMessage.cs index 1adfbb38b..4e2d91320 100644 --- a/.dotnet/src/Custom/Chat/ChatMessage.cs +++ b/.dotnet/src/Custom/Chat/ChatMessage.cs @@ -81,14 +81,7 @@ internal ChatMessage(ChatMessageRole role, IEnumerable c { foreach (ChatMessageContentPart contentPart in contentParts) { - if (contentPart.Kind == ChatMessageContentPartKind.Audio && role == ChatMessageRole.Assistant) - { - Content.Add(new ChatMessageContentPart(ChatMessageContentPartKind.Audio, audioReference: new(contentPart.AudioCorrelationId))); - } - else - { - Content.Add(contentPart); - } + Content.Add(contentPart); } } } @@ -141,6 +134,10 @@ internal ChatMessage(ChatMessageRole role, string content = null) : this(role) /// public static AssistantChatMessage CreateAssistantMessage(ChatCompletion chatCompletion) => new(chatCompletion); + + /// + public static AssistantChatMessage CreateAssistantMessage(ChatResponseAudioReference audioReference) => new(audioReference); + #endregion #region ToolChatMessage diff --git a/.dotnet/src/Custom/Chat/ChatMessageContentPart.Serialization.cs b/.dotnet/src/Custom/Chat/ChatMessageContentPart.Serialization.cs index 8afaf8151..2b8d1b2e6 100644 --- a/.dotnet/src/Custom/Chat/ChatMessageContentPart.Serialization.cs +++ b/.dotnet/src/Custom/Chat/ChatMessageContentPart.Serialization.cs @@ -14,13 +14,6 @@ void IJsonModel.Write(Utf8JsonWriter writer, ModelReader internal static void WriteCoreContentPart(ChatMessageContentPart instance, Utf8JsonWriter writer, ModelReaderWriterOptions options) { - if (instance.IsContrived) - { - throw new InvalidOperationException( - $"Synthetic {nameof(ChatMessageContentPart)} instances cannot be directly serialized. " - + $"Instead, please serialize the owner of the {nameof(ChatMessageContent)} collection."); - } - writer.WriteStartObject(); writer.WritePropertyName("type"u8); writer.WriteStringValue(instance._kind.ToSerialString()); @@ -99,6 +92,6 @@ internal static ChatMessageContentPart DeserializeChatMessageContentPart(JsonEle } } serializedAdditionalRawData = rawDataDictionary; - return new ChatMessageContentPart(kind, text, imageUri, refusal, inputAudio, outputAudio: null, responseAudioUpdate: null, audioReference: null, serializedAdditionalRawData); + return new ChatMessageContentPart(kind, text, imageUri, refusal, inputAudio, serializedAdditionalRawData); } } diff --git a/.dotnet/src/Custom/Chat/ChatMessageContentPart.cs b/.dotnet/src/Custom/Chat/ChatMessageContentPart.cs index 65bce4bd7..295ae8ac9 100644 --- a/.dotnet/src/Custom/Chat/ChatMessageContentPart.cs +++ b/.dotnet/src/Custom/Chat/ChatMessageContentPart.cs @@ -19,6 +19,10 @@ namespace OpenAI.Chat; /// Call to create a that /// encapsulates a refusal coming from the model. /// +/// +/// Call to create a content part +/// encapsulating input audio for user role messages. +/// /// /// [CodeGenModel("ChatMessageContentPart")] @@ -29,9 +33,6 @@ public partial class ChatMessageContentPart private readonly string _text; private readonly InternalChatCompletionRequestMessageContentPartImageImageUrl _imageUri; private readonly InternalChatCompletionRequestMessageContentPartAudioInputAudio _inputAudio; - private readonly InternalChatCompletionResponseMessageAudio _outputAudio; - private readonly InternalChatCompletionMessageAudioChunk _responseAudioUpdate; - private readonly InternalChatCompletionRequestAssistantMessageAudio _audioReference; private readonly string _refusal; // CUSTOM: Made internal. @@ -46,9 +47,6 @@ internal ChatMessageContentPart( InternalChatCompletionRequestMessageContentPartImageImageUrl imageUri = default, string refusal = default, InternalChatCompletionRequestMessageContentPartAudioInputAudio inputAudio = default, - InternalChatCompletionResponseMessageAudio outputAudio = default, - InternalChatCompletionMessageAudioChunk responseAudioUpdate = default, - InternalChatCompletionRequestAssistantMessageAudio audioReference = default, IDictionary serializedAdditionalRawData = default) { _kind = kind; @@ -56,9 +54,6 @@ internal ChatMessageContentPart( _imageUri = imageUri; _refusal = refusal; _inputAudio = inputAudio; - _outputAudio = outputAudio; - _responseAudioUpdate = responseAudioUpdate; - _audioReference = audioReference; _additionalBinaryDataProperties = serializedAdditionalRawData; } @@ -90,12 +85,9 @@ internal ChatMessageContentPart( /// /// /// Present when is and the content part - /// represents user role audio input or response audio content from the model. - /// - /// When streaming, this value represents the latest incremental audio update. - /// + /// represents user role audio input. /// - public BinaryData AudioBytes => _inputAudio?.Data ?? _outputAudio?.Data ?? _responseAudioUpdate?.Data; + public BinaryData AudioBytes => _inputAudio?.Data; /// /// The encoding format that the audio data provided in should be interpreted with. @@ -106,37 +98,6 @@ internal ChatMessageContentPart( /// public ChatInputAudioFormat? AudioInputFormat => _inputAudio?.Format; - /// - /// The unique identifier, as provided with model-generated response audio, that may be supplied as assistant - /// conversation history. - /// - /// - /// Present when is and the content part - /// represents response audio from the service or assistant role historical audio input. - /// - public string AudioCorrelationId => _audioReference?.Id ?? _outputAudio?.Id ?? _responseAudioUpdate?.Id; - - /// - /// The timestamp after which the audio associated with is no longer available. - /// - /// - /// Present when is and the content part - /// represents response audio from the service. - /// - public DateTimeOffset? AudioExpiresAt => _outputAudio?.ExpiresAt ?? _responseAudioUpdate?.ExpiresAt; - - /// - /// The transcript that approximates the content of the audio provided by . - /// - /// - /// Present when is and the content part - /// represents response audio from the service. - /// - /// When streaming, this value represents the latest transcript update. - /// - /// - public string AudioTranscript => _outputAudio?.Transcript ?? _responseAudioUpdate?.Transcript; - // CUSTOM: Spread. /// /// The level of detail with which the model should process the image and generate its textual understanding of @@ -210,11 +171,11 @@ public static ChatMessageContentPart CreateRefusalPart(string refusal) /// Creates a new that encapsulates user role input audio in a known format. /// /// Binary audio content parts may only be used with instances to represent user audio input. When referring to - /// past audio output from the model, use instead. + /// past audio output from the model, use instead. /// /// The audio data. /// The format of the audio data. - public static ChatMessageContentPart CreateAudioPart(BinaryData audioBytes, ChatInputAudioFormat audioFormat) + public static ChatMessageContentPart CreateInputAudioPart(BinaryData audioBytes, ChatInputAudioFormat audioFormat) { Argument.AssertNotNull(audioBytes, nameof(audioBytes)); @@ -223,22 +184,6 @@ public static ChatMessageContentPart CreateAudioPart(BinaryData audioBytes, Chat inputAudio: new(audioBytes, audioFormat)); } - /// Creates a new that encapsulates an ID-based reference to earlier response audio provided by the model. - /// - /// Reference-based audio content parts are used with instances to represent historical audio output from the model. When providing - /// user audio input, use instead. - /// - /// The unique identifier associated with the audio. - /// is null. - public static ChatMessageContentPart CreateAudioPart(string audioCorrelationId) - { - Argument.AssertNotNull(audioCorrelationId, nameof(audioCorrelationId)); - - return new( - kind: ChatMessageContentPartKind.Audio, - audioReference: new InternalChatCompletionRequestAssistantMessageAudio(audioCorrelationId, null)); - } - /// /// Implicitly intantiates a new from a . As such, /// using a in place of a is equivalent to calling the @@ -246,22 +191,4 @@ public static ChatMessageContentPart CreateAudioPart(string audioCorrelationId) /// /// The text encapsulated by this . public static implicit operator ChatMessageContentPart(string text) => CreateTextPart(text); - - /// - /// Gets a value indicating whether this content part is contrived, in which case it does not represent a valid - /// member of a JSON content array within a REST body. - /// - internal bool IsContrived - { - get - { - // Audio content parts representing ID-based references or response audio are synthesized from dedicated - // "audio" JSON properties. - if (_kind == ChatMessageContentPartKind.Audio && _inputAudio is null) - { - return true; - } - return false; - } - } } diff --git a/.dotnet/src/Custom/Chat/ChatResponseAudio.cs b/.dotnet/src/Custom/Chat/ChatResponseAudio.cs new file mode 100644 index 000000000..54f1fe210 --- /dev/null +++ b/.dotnet/src/Custom/Chat/ChatResponseAudio.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using System.Text.RegularExpressions; + +namespace OpenAI.Chat; + +[CodeGenModel("ChatCompletionResponseMessageAudio")] +public partial class ChatResponseAudio +{ + +} \ No newline at end of file diff --git a/.dotnet/src/Custom/Chat/ChatResponseAudioReference.cs b/.dotnet/src/Custom/Chat/ChatResponseAudioReference.cs new file mode 100644 index 000000000..2a6465202 --- /dev/null +++ b/.dotnet/src/Custom/Chat/ChatResponseAudioReference.cs @@ -0,0 +1,10 @@ +using System; +using System.Collections.Generic; +using System.Text.RegularExpressions; + +namespace OpenAI.Chat; + +[CodeGenModel("ChatCompletionRequestAssistantMessageAudio")] +public partial class ChatResponseAudioReference +{ +} \ No newline at end of file diff --git a/.dotnet/src/Custom/Chat/Internal/GeneratorStubs.cs b/.dotnet/src/Custom/Chat/Internal/GeneratorStubs.cs index 2f64daf92..6a2a78016 100644 --- a/.dotnet/src/Custom/Chat/Internal/GeneratorStubs.cs +++ b/.dotnet/src/Custom/Chat/Internal/GeneratorStubs.cs @@ -102,12 +102,6 @@ internal readonly partial struct InternalCreateChatCompletionRequestModality { } [CodeGenModel("ChatCompletionRequestMessageContentPartAudioType")] internal readonly partial struct InternalChatCompletionRequestMessageContentPartAudioType { } -[CodeGenModel("ChatCompletionRequestAssistantMessageAudio")] -internal partial class InternalChatCompletionRequestAssistantMessageAudio { } - -[CodeGenModel("ChatCompletionResponseMessageAudio")] -internal partial class InternalChatCompletionResponseMessageAudio { } - [CodeGenModel("ChatCompletionMessageAudioChunk")] internal partial class InternalChatCompletionMessageAudioChunk { } diff --git a/.dotnet/src/Custom/Chat/Internal/InternalChatCompletionStreamResponseDelta.cs b/.dotnet/src/Custom/Chat/Internal/InternalChatCompletionStreamResponseDelta.cs index 80c111996..171de51a5 100644 --- a/.dotnet/src/Custom/Chat/Internal/InternalChatCompletionStreamResponseDelta.cs +++ b/.dotnet/src/Custom/Chat/Internal/InternalChatCompletionStreamResponseDelta.cs @@ -16,4 +16,9 @@ internal partial class InternalChatCompletionStreamResponseDelta /// The contents of the message. [CodeGenMember("Content")] public ChatMessageContent Content { get; } + + // CUSTOM: Changed type to share with non-streaming response audio. + /// The incremental response audio information for the message. + [CodeGenMember("Audio")] + public ChatResponseAudio Audio { get; } } diff --git a/.dotnet/src/Custom/Chat/OpenAIChatModelFactory.cs b/.dotnet/src/Custom/Chat/OpenAIChatModelFactory.cs index aeabf2630..3b87a1416 100644 --- a/.dotnet/src/Custom/Chat/OpenAIChatModelFactory.cs +++ b/.dotnet/src/Custom/Chat/OpenAIChatModelFactory.cs @@ -23,26 +23,17 @@ public static ChatCompletion ChatCompletion( string model = null, string systemFingerprint = null, ChatTokenUsage usage = null, - BinaryData audioBytes = null, - string audioCorrelationId = null, - string audioTranscript = null, - DateTimeOffset? audioExpiresAt = null) + ChatResponseAudio responseAudio = null) { content ??= new ChatMessageContent(); toolCalls ??= new List(); contentTokenLogProbabilities ??= new List(); refusalTokenLogProbabilities ??= new List(); - InternalChatCompletionResponseMessageAudio audio = null; - if (audioCorrelationId is not null || audioExpiresAt is not null || audioBytes is not null || audioTranscript is not null) - { - audio = new(audioCorrelationId, audioExpiresAt ?? default, audioBytes, audioTranscript); - } - InternalChatCompletionResponseMessage message = new InternalChatCompletionResponseMessage( refusal, toolCalls.ToList(), - audio, + responseAudio, role, content, functionCall, @@ -74,6 +65,7 @@ public static ChatCompletion ChatCompletion( additionalBinaryDataProperties: null); } + /// Initializes a new instance of . /// A new instance for mocking. public static ChatTokenLogProbabilityDetails ChatTokenLogProbabilityDetails(string token = null, float logProbability = default, ReadOnlyMemory? utf8Bytes = null, IEnumerable topLogProbabilities = null) @@ -129,6 +121,16 @@ public static ChatOutputTokenUsageDetails ChatOutputTokenUsageDetails(int reason return new ChatOutputTokenUsageDetails(reasoningTokenCount, audioTokenCount, additionalBinaryDataProperties: null); } + public static ChatResponseAudio ChatResponseAudio(BinaryData data, string id = null, string transcript = null, DateTimeOffset expiresAt = default) + { + return new ChatResponseAudio( + id, + expiresAt, + data, + transcript, + additionalBinaryDataProperties: null); + } + /// Initializes a new instance of . /// A new instance for mocking. public static StreamingChatCompletionUpdate StreamingChatCompletionUpdate( @@ -145,29 +147,20 @@ public static StreamingChatCompletionUpdate StreamingChatCompletionUpdate( string model = null, string systemFingerprint = null, ChatTokenUsage usage = null, - string audioCorrelationId = null, - string audioTranscript = null, - BinaryData audioBytes = null, - DateTimeOffset? audioExpiresAt = null) + ChatResponseAudio responseAudio = null) { contentUpdate ??= new ChatMessageContent(); toolCallUpdates ??= new List(); contentTokenLogProbabilities ??= new List(); refusalTokenLogProbabilities ??= new List(); - InternalChatCompletionMessageAudioChunk audio = null; - if (audioCorrelationId is not null || audioTranscript is not null || audioBytes is not null || audioExpiresAt is not null) - { - audio = new(audioCorrelationId, audioTranscript, audioBytes, audioExpiresAt, additionalBinaryDataProperties: null); - } - InternalChatCompletionStreamResponseDelta delta = new InternalChatCompletionStreamResponseDelta( - audio, functionCallUpdate, toolCallUpdates.ToList(), refusalUpdate, role, contentUpdate, + responseAudio, additionalBinaryDataProperties: null); InternalCreateChatCompletionStreamResponseChoiceLogprobs logprobs = new InternalCreateChatCompletionStreamResponseChoiceLogprobs( diff --git a/.dotnet/src/Custom/Chat/Streaming/StreamingChatCompletionUpdate.cs b/.dotnet/src/Custom/Chat/Streaming/StreamingChatCompletionUpdate.cs index 01a5ec011..1707f1aaf 100644 --- a/.dotnet/src/Custom/Chat/Streaming/StreamingChatCompletionUpdate.cs +++ b/.dotnet/src/Custom/Chat/Streaming/StreamingChatCompletionUpdate.cs @@ -93,7 +93,7 @@ public partial class StreamingChatCompletionUpdate /// Each streaming update contains only a small portion of tokens. To reconstitute the entire chat completion, /// all values across streaming updates must be combined. /// - public ChatMessageContent ContentUpdate => _contentUpdate ??= GetWrappedContentUpdate(); + public ChatMessageContent ContentUpdate => _contentUpdate ??= InternalChoiceDelta?.Content ?? []; // CUSTOM: Flattened choice delta property. /// The tool calls generated by the model, such as function calls. @@ -110,21 +110,5 @@ public IReadOnlyList ToolCallUpdates [Obsolete($"This property is obsolete. Please use {nameof(ToolCallUpdates)} instead.")] public StreamingChatFunctionCallUpdate FunctionCallUpdate => InternalChoiceDelta?.FunctionCall; - private ChatMessageContent GetWrappedContentUpdate() - { - ChatMessageContent wrappedResult = new(); - - if (InternalChoiceDelta?.Audio is not null) - { - wrappedResult.Add(new ChatMessageContentPart( - ChatMessageContentPartKind.Audio, - responseAudioUpdate: InternalChoiceDelta.Audio)); - } - foreach (ChatMessageContentPart deltaContentPart in InternalChoiceDelta?.Content ?? []) - { - wrappedResult.Add(deltaContentPart); - } - - return wrappedResult; - } + public ChatResponseAudio ResponseAudio => InternalChoiceDelta?.Audio; } diff --git a/.dotnet/src/Generated/Models/AssistantChatMessage.Serialization.cs b/.dotnet/src/Generated/Models/AssistantChatMessage.Serialization.cs index b4782f168..45fab83f5 100644 --- a/.dotnet/src/Generated/Models/AssistantChatMessage.Serialization.cs +++ b/.dotnet/src/Generated/Models/AssistantChatMessage.Serialization.cs @@ -5,6 +5,7 @@ using System; using System.ClientModel; using System.ClientModel.Primitives; +using System.Collections.Generic; using System.Text.Json; using OpenAI; @@ -59,12 +60,12 @@ protected override void JsonModelWriteCore(Utf8JsonWriter writer, ModelReaderWri writer.WriteNull("functionCall"u8); } } - if (Optional.IsDefined(Audio) && _additionalBinaryDataProperties?.ContainsKey("audio") != true) + if (Optional.IsDefined(ResponseAudioReference) && _additionalBinaryDataProperties?.ContainsKey("audio") != true) { - if (Audio != null) + if (ResponseAudioReference != null) { writer.WritePropertyName("audio"u8); - writer.WriteObjectValue(Audio, options); + writer.WriteObjectValue(ResponseAudioReference, options); } else { @@ -83,7 +84,99 @@ protected override ChatMessage JsonModelCreateCore(ref Utf8JsonReader reader, Mo throw new FormatException($"The model {nameof(AssistantChatMessage)} does not support reading '{format}' format."); } using JsonDocument document = JsonDocument.ParseValue(ref reader); - return AssistantChatMessage.DeserializeAssistantChatMessage(document.RootElement, options); + return DeserializeAssistantChatMessage(document.RootElement, options); + } + + internal static AssistantChatMessage DeserializeAssistantChatMessage(JsonElement element, ModelReaderWriterOptions options) + { + if (element.ValueKind == JsonValueKind.Null) + { + return null; + } + ChatMessageContent content = default; + Chat.ChatMessageRole role = default; + IDictionary additionalBinaryDataProperties = new ChangeTrackingDictionary(); + string refusal = default; + string participantName = default; + IList toolCalls = default; + ChatFunctionCall functionCall = default; + ChatResponseAudioReference responseAudioReference = default; + foreach (var prop in element.EnumerateObject()) + { + if (prop.NameEquals("content"u8)) + { + DeserializeContentValue(prop, ref content); + continue; + } + if (prop.NameEquals("role"u8)) + { + role = prop.Value.GetString().ToChatMessageRole(); + continue; + } + if (prop.NameEquals("refusal"u8)) + { + if (prop.Value.ValueKind == JsonValueKind.Null) + { + refusal = null; + continue; + } + refusal = prop.Value.GetString(); + continue; + } + if (prop.NameEquals("name"u8)) + { + participantName = prop.Value.GetString(); + continue; + } + if (prop.NameEquals("tool_calls"u8)) + { + if (prop.Value.ValueKind == JsonValueKind.Null) + { + continue; + } + List array = new List(); + foreach (var item in prop.Value.EnumerateArray()) + { + array.Add(ChatToolCall.DeserializeChatToolCall(item, options)); + } + toolCalls = array; + continue; + } + if (prop.NameEquals("function_call"u8)) + { + if (prop.Value.ValueKind == JsonValueKind.Null) + { + functionCall = null; + continue; + } + functionCall = ChatFunctionCall.DeserializeChatFunctionCall(prop.Value, options); + continue; + } + if (prop.NameEquals("audio"u8)) + { + if (prop.Value.ValueKind == JsonValueKind.Null) + { + responseAudioReference = null; + continue; + } + responseAudioReference = ChatResponseAudioReference.DeserializeChatResponseAudioReference(prop.Value, options); + continue; + } + if (true) + { + additionalBinaryDataProperties.Add(prop.Name, BinaryData.FromString(prop.Value.GetRawText())); + } + } + // CUSTOM: Initialize Content collection property. + return new AssistantChatMessage( + content ?? new ChatMessageContent(), + role, + additionalBinaryDataProperties, + refusal, + participantName, + toolCalls ?? new ChangeTrackingList(), + functionCall, + responseAudioReference); } BinaryData IPersistableModel.Write(ModelReaderWriterOptions options) => PersistableModelWriteCore(options); @@ -110,7 +203,7 @@ protected override ChatMessage PersistableModelCreateCore(BinaryData data, Model case "J": using (JsonDocument document = JsonDocument.Parse(data)) { - return AssistantChatMessage.DeserializeAssistantChatMessage(document.RootElement, options); + return DeserializeAssistantChatMessage(document.RootElement, options); } default: throw new FormatException($"The model {nameof(AssistantChatMessage)} does not support reading '{options.Format}' format."); @@ -132,7 +225,7 @@ public static explicit operator AssistantChatMessage(ClientResult result) { using PipelineResponse response = result.GetRawResponse(); using JsonDocument document = JsonDocument.Parse(response.Content); - return AssistantChatMessage.DeserializeAssistantChatMessage(document.RootElement, ModelSerializationExtensions.WireOptions); + return DeserializeAssistantChatMessage(document.RootElement, ModelSerializationExtensions.WireOptions); } } } diff --git a/.dotnet/src/Generated/Models/AssistantChatMessage.cs b/.dotnet/src/Generated/Models/AssistantChatMessage.cs index d84662667..6975e7bd3 100644 --- a/.dotnet/src/Generated/Models/AssistantChatMessage.cs +++ b/.dotnet/src/Generated/Models/AssistantChatMessage.cs @@ -9,13 +9,13 @@ namespace OpenAI.Chat { public partial class AssistantChatMessage : ChatMessage { - internal AssistantChatMessage(ChatMessageContent content, Chat.ChatMessageRole role, IDictionary additionalBinaryDataProperties, string refusal, string participantName, IList toolCalls, ChatFunctionCall functionCall, InternalChatCompletionRequestAssistantMessageAudio audio) : base(content, role, additionalBinaryDataProperties) + internal AssistantChatMessage(ChatMessageContent content, Chat.ChatMessageRole role, IDictionary additionalBinaryDataProperties, string refusal, string participantName, IList toolCalls, ChatFunctionCall functionCall, ChatResponseAudioReference responseAudioReference) : base(content, role, additionalBinaryDataProperties) { Refusal = refusal; ParticipantName = participantName; ToolCalls = toolCalls; FunctionCall = functionCall; - Audio = audio; + ResponseAudioReference = responseAudioReference; } public string Refusal { get; set; } diff --git a/.dotnet/src/Generated/Models/InternalChatCompletionResponseMessageAudio.Serialization.cs b/.dotnet/src/Generated/Models/ChatResponseAudio.Serialization.cs similarity index 59% rename from .dotnet/src/Generated/Models/InternalChatCompletionResponseMessageAudio.Serialization.cs rename to .dotnet/src/Generated/Models/ChatResponseAudio.Serialization.cs index 83bbc49e6..40e982773 100644 --- a/.dotnet/src/Generated/Models/InternalChatCompletionResponseMessageAudio.Serialization.cs +++ b/.dotnet/src/Generated/Models/ChatResponseAudio.Serialization.cs @@ -11,13 +11,13 @@ namespace OpenAI.Chat { - internal partial class InternalChatCompletionResponseMessageAudio : IJsonModel + public partial class ChatResponseAudio : IJsonModel { - internal InternalChatCompletionResponseMessageAudio() + internal ChatResponseAudio() { } - void IJsonModel.Write(Utf8JsonWriter writer, ModelReaderWriterOptions options) + void IJsonModel.Write(Utf8JsonWriter writer, ModelReaderWriterOptions options) { writer.WriteStartObject(); JsonModelWriteCore(writer, options); @@ -26,10 +26,10 @@ void IJsonModel.Write(Utf8JsonWriter protected virtual void JsonModelWriteCore(Utf8JsonWriter writer, ModelReaderWriterOptions options) { - string format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; + string format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; if (format != "J") { - throw new FormatException($"The model {nameof(InternalChatCompletionResponseMessageAudio)} does not support writing '{format}' format."); + throw new FormatException($"The model {nameof(ChatResponseAudio)} does not support writing '{format}' format."); } if (_additionalBinaryDataProperties?.ContainsKey("id") != true) { @@ -72,20 +72,20 @@ protected virtual void JsonModelWriteCore(Utf8JsonWriter writer, ModelReaderWrit } } - InternalChatCompletionResponseMessageAudio IJsonModel.Create(ref Utf8JsonReader reader, ModelReaderWriterOptions options) => JsonModelCreateCore(ref reader, options); + ChatResponseAudio IJsonModel.Create(ref Utf8JsonReader reader, ModelReaderWriterOptions options) => JsonModelCreateCore(ref reader, options); - protected virtual InternalChatCompletionResponseMessageAudio JsonModelCreateCore(ref Utf8JsonReader reader, ModelReaderWriterOptions options) + protected virtual ChatResponseAudio JsonModelCreateCore(ref Utf8JsonReader reader, ModelReaderWriterOptions options) { - string format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; + string format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; if (format != "J") { - throw new FormatException($"The model {nameof(InternalChatCompletionResponseMessageAudio)} does not support reading '{format}' format."); + throw new FormatException($"The model {nameof(ChatResponseAudio)} does not support reading '{format}' format."); } using JsonDocument document = JsonDocument.ParseValue(ref reader); - return DeserializeInternalChatCompletionResponseMessageAudio(document.RootElement, options); + return DeserializeChatResponseAudio(document.RootElement, options); } - internal static InternalChatCompletionResponseMessageAudio DeserializeInternalChatCompletionResponseMessageAudio(JsonElement element, ModelReaderWriterOptions options) + internal static ChatResponseAudio DeserializeChatResponseAudio(JsonElement element, ModelReaderWriterOptions options) { if (element.ValueKind == JsonValueKind.Null) { @@ -123,56 +123,56 @@ internal static InternalChatCompletionResponseMessageAudio DeserializeInternalCh additionalBinaryDataProperties.Add(prop.Name, BinaryData.FromString(prop.Value.GetRawText())); } } - return new InternalChatCompletionResponseMessageAudio(id, expiresAt, data, transcript, additionalBinaryDataProperties); + return new ChatResponseAudio(id, expiresAt, data, transcript, additionalBinaryDataProperties); } - BinaryData IPersistableModel.Write(ModelReaderWriterOptions options) => PersistableModelWriteCore(options); + BinaryData IPersistableModel.Write(ModelReaderWriterOptions options) => PersistableModelWriteCore(options); protected virtual BinaryData PersistableModelWriteCore(ModelReaderWriterOptions options) { - string format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; + string 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(InternalChatCompletionResponseMessageAudio)} does not support writing '{options.Format}' format."); + throw new FormatException($"The model {nameof(ChatResponseAudio)} does not support writing '{options.Format}' format."); } } - InternalChatCompletionResponseMessageAudio IPersistableModel.Create(BinaryData data, ModelReaderWriterOptions options) => PersistableModelCreateCore(data, options); + ChatResponseAudio IPersistableModel.Create(BinaryData data, ModelReaderWriterOptions options) => PersistableModelCreateCore(data, options); - protected virtual InternalChatCompletionResponseMessageAudio PersistableModelCreateCore(BinaryData data, ModelReaderWriterOptions options) + protected virtual ChatResponseAudio PersistableModelCreateCore(BinaryData data, ModelReaderWriterOptions options) { - string format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; + string format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; switch (format) { case "J": using (JsonDocument document = JsonDocument.Parse(data)) { - return DeserializeInternalChatCompletionResponseMessageAudio(document.RootElement, options); + return DeserializeChatResponseAudio(document.RootElement, options); } default: - throw new FormatException($"The model {nameof(InternalChatCompletionResponseMessageAudio)} does not support reading '{options.Format}' format."); + throw new FormatException($"The model {nameof(ChatResponseAudio)} does not support reading '{options.Format}' format."); } } - string IPersistableModel.GetFormatFromOptions(ModelReaderWriterOptions options) => "J"; + string IPersistableModel.GetFormatFromOptions(ModelReaderWriterOptions options) => "J"; - public static implicit operator BinaryContent(InternalChatCompletionResponseMessageAudio internalChatCompletionResponseMessageAudio) + public static implicit operator BinaryContent(ChatResponseAudio chatResponseAudio) { - if (internalChatCompletionResponseMessageAudio == null) + if (chatResponseAudio == null) { return null; } - return BinaryContent.Create(internalChatCompletionResponseMessageAudio, ModelSerializationExtensions.WireOptions); + return BinaryContent.Create(chatResponseAudio, ModelSerializationExtensions.WireOptions); } - public static explicit operator InternalChatCompletionResponseMessageAudio(ClientResult result) + public static explicit operator ChatResponseAudio(ClientResult result) { using PipelineResponse response = result.GetRawResponse(); using JsonDocument document = JsonDocument.Parse(response.Content); - return DeserializeInternalChatCompletionResponseMessageAudio(document.RootElement, ModelSerializationExtensions.WireOptions); + return DeserializeChatResponseAudio(document.RootElement, ModelSerializationExtensions.WireOptions); } } } diff --git a/.dotnet/src/Generated/Models/InternalChatCompletionResponseMessageAudio.cs b/.dotnet/src/Generated/Models/ChatResponseAudio.cs similarity index 70% rename from .dotnet/src/Generated/Models/InternalChatCompletionResponseMessageAudio.cs rename to .dotnet/src/Generated/Models/ChatResponseAudio.cs index 28ce7fb60..395622cac 100644 --- a/.dotnet/src/Generated/Models/InternalChatCompletionResponseMessageAudio.cs +++ b/.dotnet/src/Generated/Models/ChatResponseAudio.cs @@ -7,11 +7,11 @@ namespace OpenAI.Chat { - internal partial class InternalChatCompletionResponseMessageAudio + public partial class ChatResponseAudio { private protected IDictionary _additionalBinaryDataProperties; - internal InternalChatCompletionResponseMessageAudio(string id, DateTimeOffset expiresAt, BinaryData data, string transcript) + internal ChatResponseAudio(string id, DateTimeOffset expiresAt, BinaryData data, string transcript) { Id = id; ExpiresAt = expiresAt; @@ -19,7 +19,7 @@ internal InternalChatCompletionResponseMessageAudio(string id, DateTimeOffset ex Transcript = transcript; } - internal InternalChatCompletionResponseMessageAudio(string id, DateTimeOffset expiresAt, BinaryData data, string transcript, IDictionary additionalBinaryDataProperties) + internal ChatResponseAudio(string id, DateTimeOffset expiresAt, BinaryData data, string transcript, IDictionary additionalBinaryDataProperties) { Id = id; ExpiresAt = expiresAt; diff --git a/.dotnet/src/Generated/Models/ChatResponseAudioReference.Serialization.cs b/.dotnet/src/Generated/Models/ChatResponseAudioReference.Serialization.cs new file mode 100644 index 000000000..57bf8862f --- /dev/null +++ b/.dotnet/src/Generated/Models/ChatResponseAudioReference.Serialization.cs @@ -0,0 +1,145 @@ +// + +#nullable disable + +using System; +using System.ClientModel; +using System.ClientModel.Primitives; +using System.Collections.Generic; +using System.Text.Json; +using OpenAI; + +namespace OpenAI.Chat +{ + public partial class ChatResponseAudioReference : IJsonModel + { + internal ChatResponseAudioReference() + { + } + + void IJsonModel.Write(Utf8JsonWriter writer, ModelReaderWriterOptions options) + { + writer.WriteStartObject(); + JsonModelWriteCore(writer, options); + writer.WriteEndObject(); + } + + protected virtual void JsonModelWriteCore(Utf8JsonWriter writer, ModelReaderWriterOptions options) + { + string format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; + if (format != "J") + { + throw new FormatException($"The model {nameof(ChatResponseAudioReference)} does not support writing '{format}' format."); + } + if (_additionalBinaryDataProperties?.ContainsKey("id") != true) + { + writer.WritePropertyName("id"u8); + writer.WriteStringValue(Id); + } + if (true && _additionalBinaryDataProperties != null) + { + foreach (var item in _additionalBinaryDataProperties) + { + if (ModelSerializationExtensions.IsSentinelValue(item.Value)) + { + continue; + } + 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 + } + } + } + + ChatResponseAudioReference IJsonModel.Create(ref Utf8JsonReader reader, ModelReaderWriterOptions options) => JsonModelCreateCore(ref reader, options); + + protected virtual ChatResponseAudioReference JsonModelCreateCore(ref Utf8JsonReader reader, ModelReaderWriterOptions options) + { + string format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; + if (format != "J") + { + throw new FormatException($"The model {nameof(ChatResponseAudioReference)} does not support reading '{format}' format."); + } + using JsonDocument document = JsonDocument.ParseValue(ref reader); + return DeserializeChatResponseAudioReference(document.RootElement, options); + } + + internal static ChatResponseAudioReference DeserializeChatResponseAudioReference(JsonElement element, ModelReaderWriterOptions options) + { + if (element.ValueKind == JsonValueKind.Null) + { + return null; + } + string id = default; + IDictionary additionalBinaryDataProperties = new ChangeTrackingDictionary(); + foreach (var prop in element.EnumerateObject()) + { + if (prop.NameEquals("id"u8)) + { + id = prop.Value.GetString(); + continue; + } + if (true) + { + additionalBinaryDataProperties.Add(prop.Name, BinaryData.FromString(prop.Value.GetRawText())); + } + } + return new ChatResponseAudioReference(id, additionalBinaryDataProperties); + } + + BinaryData IPersistableModel.Write(ModelReaderWriterOptions options) => PersistableModelWriteCore(options); + + protected virtual BinaryData PersistableModelWriteCore(ModelReaderWriterOptions options) + { + string 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(ChatResponseAudioReference)} does not support writing '{options.Format}' format."); + } + } + + ChatResponseAudioReference IPersistableModel.Create(BinaryData data, ModelReaderWriterOptions options) => PersistableModelCreateCore(data, options); + + protected virtual ChatResponseAudioReference PersistableModelCreateCore(BinaryData data, ModelReaderWriterOptions options) + { + string format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; + switch (format) + { + case "J": + using (JsonDocument document = JsonDocument.Parse(data)) + { + return DeserializeChatResponseAudioReference(document.RootElement, options); + } + default: + throw new FormatException($"The model {nameof(ChatResponseAudioReference)} does not support reading '{options.Format}' format."); + } + } + + string IPersistableModel.GetFormatFromOptions(ModelReaderWriterOptions options) => "J"; + + public static implicit operator BinaryContent(ChatResponseAudioReference chatResponseAudioReference) + { + if (chatResponseAudioReference == null) + { + return null; + } + return BinaryContent.Create(chatResponseAudioReference, ModelSerializationExtensions.WireOptions); + } + + public static explicit operator ChatResponseAudioReference(ClientResult result) + { + using PipelineResponse response = result.GetRawResponse(); + using JsonDocument document = JsonDocument.Parse(response.Content); + return DeserializeChatResponseAudioReference(document.RootElement, ModelSerializationExtensions.WireOptions); + } + } +} diff --git a/.dotnet/src/Generated/Models/InternalChatCompletionRequestAssistantMessageAudio.cs b/.dotnet/src/Generated/Models/ChatResponseAudioReference.cs similarity index 69% rename from .dotnet/src/Generated/Models/InternalChatCompletionRequestAssistantMessageAudio.cs rename to .dotnet/src/Generated/Models/ChatResponseAudioReference.cs index 9976eda78..6ae6cc6fa 100644 --- a/.dotnet/src/Generated/Models/InternalChatCompletionRequestAssistantMessageAudio.cs +++ b/.dotnet/src/Generated/Models/ChatResponseAudioReference.cs @@ -8,18 +8,18 @@ namespace OpenAI.Chat { - internal partial class InternalChatCompletionRequestAssistantMessageAudio + public partial class ChatResponseAudioReference { private protected IDictionary _additionalBinaryDataProperties; - public InternalChatCompletionRequestAssistantMessageAudio(string id) + public ChatResponseAudioReference(string id) { Argument.AssertNotNull(id, nameof(id)); Id = id; } - internal InternalChatCompletionRequestAssistantMessageAudio(string id, IDictionary additionalBinaryDataProperties) + internal ChatResponseAudioReference(string id, IDictionary additionalBinaryDataProperties) { Id = id; _additionalBinaryDataProperties = additionalBinaryDataProperties; diff --git a/.dotnet/src/Generated/Models/InternalChatCompletionRequestAssistantMessageAudio.Serialization.cs b/.dotnet/src/Generated/Models/InternalChatCompletionRequestAssistantMessageAudio.Serialization.cs deleted file mode 100644 index ffc87fbb0..000000000 --- a/.dotnet/src/Generated/Models/InternalChatCompletionRequestAssistantMessageAudio.Serialization.cs +++ /dev/null @@ -1,145 +0,0 @@ -// - -#nullable disable - -using System; -using System.ClientModel; -using System.ClientModel.Primitives; -using System.Collections.Generic; -using System.Text.Json; -using OpenAI; - -namespace OpenAI.Chat -{ - internal partial class InternalChatCompletionRequestAssistantMessageAudio : IJsonModel - { - internal InternalChatCompletionRequestAssistantMessageAudio() - { - } - - void IJsonModel.Write(Utf8JsonWriter writer, ModelReaderWriterOptions options) - { - writer.WriteStartObject(); - JsonModelWriteCore(writer, options); - writer.WriteEndObject(); - } - - protected virtual void JsonModelWriteCore(Utf8JsonWriter writer, ModelReaderWriterOptions options) - { - string format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; - if (format != "J") - { - throw new FormatException($"The model {nameof(InternalChatCompletionRequestAssistantMessageAudio)} does not support writing '{format}' format."); - } - if (_additionalBinaryDataProperties?.ContainsKey("id") != true) - { - writer.WritePropertyName("id"u8); - writer.WriteStringValue(Id); - } - if (true && _additionalBinaryDataProperties != null) - { - foreach (var item in _additionalBinaryDataProperties) - { - if (ModelSerializationExtensions.IsSentinelValue(item.Value)) - { - continue; - } - 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 - } - } - } - - InternalChatCompletionRequestAssistantMessageAudio IJsonModel.Create(ref Utf8JsonReader reader, ModelReaderWriterOptions options) => JsonModelCreateCore(ref reader, options); - - protected virtual InternalChatCompletionRequestAssistantMessageAudio JsonModelCreateCore(ref Utf8JsonReader reader, ModelReaderWriterOptions options) - { - string format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; - if (format != "J") - { - throw new FormatException($"The model {nameof(InternalChatCompletionRequestAssistantMessageAudio)} does not support reading '{format}' format."); - } - using JsonDocument document = JsonDocument.ParseValue(ref reader); - return DeserializeInternalChatCompletionRequestAssistantMessageAudio(document.RootElement, options); - } - - internal static InternalChatCompletionRequestAssistantMessageAudio DeserializeInternalChatCompletionRequestAssistantMessageAudio(JsonElement element, ModelReaderWriterOptions options) - { - if (element.ValueKind == JsonValueKind.Null) - { - return null; - } - string id = default; - IDictionary additionalBinaryDataProperties = new ChangeTrackingDictionary(); - foreach (var prop in element.EnumerateObject()) - { - if (prop.NameEquals("id"u8)) - { - id = prop.Value.GetString(); - continue; - } - if (true) - { - additionalBinaryDataProperties.Add(prop.Name, BinaryData.FromString(prop.Value.GetRawText())); - } - } - return new InternalChatCompletionRequestAssistantMessageAudio(id, additionalBinaryDataProperties); - } - - BinaryData IPersistableModel.Write(ModelReaderWriterOptions options) => PersistableModelWriteCore(options); - - protected virtual BinaryData PersistableModelWriteCore(ModelReaderWriterOptions options) - { - string 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(InternalChatCompletionRequestAssistantMessageAudio)} does not support writing '{options.Format}' format."); - } - } - - InternalChatCompletionRequestAssistantMessageAudio IPersistableModel.Create(BinaryData data, ModelReaderWriterOptions options) => PersistableModelCreateCore(data, options); - - protected virtual InternalChatCompletionRequestAssistantMessageAudio PersistableModelCreateCore(BinaryData data, ModelReaderWriterOptions options) - { - string format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; - switch (format) - { - case "J": - using (JsonDocument document = JsonDocument.Parse(data)) - { - return DeserializeInternalChatCompletionRequestAssistantMessageAudio(document.RootElement, options); - } - default: - throw new FormatException($"The model {nameof(InternalChatCompletionRequestAssistantMessageAudio)} does not support reading '{options.Format}' format."); - } - } - - string IPersistableModel.GetFormatFromOptions(ModelReaderWriterOptions options) => "J"; - - public static implicit operator BinaryContent(InternalChatCompletionRequestAssistantMessageAudio internalChatCompletionRequestAssistantMessageAudio) - { - if (internalChatCompletionRequestAssistantMessageAudio == null) - { - return null; - } - return BinaryContent.Create(internalChatCompletionRequestAssistantMessageAudio, ModelSerializationExtensions.WireOptions); - } - - public static explicit operator InternalChatCompletionRequestAssistantMessageAudio(ClientResult result) - { - using PipelineResponse response = result.GetRawResponse(); - using JsonDocument document = JsonDocument.Parse(response.Content); - return DeserializeInternalChatCompletionRequestAssistantMessageAudio(document.RootElement, ModelSerializationExtensions.WireOptions); - } - } -} diff --git a/.dotnet/src/Generated/Models/InternalChatCompletionResponseMessage.Serialization.cs b/.dotnet/src/Generated/Models/InternalChatCompletionResponseMessage.Serialization.cs index 25f435f56..c54451765 100644 --- a/.dotnet/src/Generated/Models/InternalChatCompletionResponseMessage.Serialization.cs +++ b/.dotnet/src/Generated/Models/InternalChatCompletionResponseMessage.Serialization.cs @@ -129,7 +129,7 @@ internal static InternalChatCompletionResponseMessage DeserializeInternalChatCom } string refusal = default; IReadOnlyList toolCalls = default; - InternalChatCompletionResponseMessageAudio audio = default; + ChatResponseAudio audio = default; Chat.ChatMessageRole role = default; ChatMessageContent content = default; ChatFunctionCall functionCall = default; @@ -167,7 +167,7 @@ internal static InternalChatCompletionResponseMessage DeserializeInternalChatCom audio = null; continue; } - audio = InternalChatCompletionResponseMessageAudio.DeserializeInternalChatCompletionResponseMessageAudio(prop.Value, options); + audio = ChatResponseAudio.DeserializeChatResponseAudio(prop.Value, options); continue; } if (prop.NameEquals("role"u8)) diff --git a/.dotnet/src/Generated/Models/InternalChatCompletionResponseMessage.cs b/.dotnet/src/Generated/Models/InternalChatCompletionResponseMessage.cs index 0afef0e57..831039448 100644 --- a/.dotnet/src/Generated/Models/InternalChatCompletionResponseMessage.cs +++ b/.dotnet/src/Generated/Models/InternalChatCompletionResponseMessage.cs @@ -19,7 +19,7 @@ internal InternalChatCompletionResponseMessage(string refusal, ChatMessageConten Content = content; } - internal InternalChatCompletionResponseMessage(string refusal, IReadOnlyList toolCalls, InternalChatCompletionResponseMessageAudio audio, Chat.ChatMessageRole role, ChatMessageContent content, ChatFunctionCall functionCall, IDictionary additionalBinaryDataProperties) + internal InternalChatCompletionResponseMessage(string refusal, IReadOnlyList toolCalls, ChatResponseAudio audio, Chat.ChatMessageRole role, ChatMessageContent content, ChatFunctionCall functionCall, IDictionary additionalBinaryDataProperties) { Refusal = refusal; ToolCalls = toolCalls; @@ -34,7 +34,7 @@ internal InternalChatCompletionResponseMessage(string refusal, IReadOnlyList ToolCalls { get; } - public InternalChatCompletionResponseMessageAudio Audio { get; } + public ChatResponseAudio Audio { get; } internal IDictionary SerializedAdditionalRawData { diff --git a/.dotnet/src/Generated/Models/InternalChatCompletionStreamResponseDelta.Serialization.cs b/.dotnet/src/Generated/Models/InternalChatCompletionStreamResponseDelta.Serialization.cs index e7d9267ba..217b7e4b1 100644 --- a/.dotnet/src/Generated/Models/InternalChatCompletionStreamResponseDelta.Serialization.cs +++ b/.dotnet/src/Generated/Models/InternalChatCompletionStreamResponseDelta.Serialization.cs @@ -27,11 +27,6 @@ protected virtual void JsonModelWriteCore(Utf8JsonWriter writer, ModelReaderWrit { throw new FormatException($"The model {nameof(InternalChatCompletionStreamResponseDelta)} does not support writing '{format}' format."); } - if (Optional.IsDefined(Audio) && _additionalBinaryDataProperties?.ContainsKey("audio") != true) - { - writer.WritePropertyName("audio"u8); - writer.WriteObjectValue(Audio, options); - } if (Optional.IsDefined(FunctionCall) && _additionalBinaryDataProperties?.ContainsKey("function_call") != true) { writer.WritePropertyName("function_call"u8); @@ -77,6 +72,11 @@ protected virtual void JsonModelWriteCore(Utf8JsonWriter writer, ModelReaderWrit writer.WriteNull("content"u8); } } + if (Optional.IsDefined(Audio) && _additionalBinaryDataProperties?.ContainsKey("audio") != true) + { + writer.WritePropertyName("audio"u8); + writer.WriteObjectValue(Audio, options); + } if (true && _additionalBinaryDataProperties != null) { foreach (var item in _additionalBinaryDataProperties) @@ -117,24 +117,15 @@ internal static InternalChatCompletionStreamResponseDelta DeserializeInternalCha { return null; } - InternalChatCompletionMessageAudioChunk audio = default; StreamingChatFunctionCallUpdate functionCall = default; IReadOnlyList toolCalls = default; string refusal = default; Chat.ChatMessageRole? role = default; ChatMessageContent content = default; + ChatResponseAudio audio = default; IDictionary additionalBinaryDataProperties = new ChangeTrackingDictionary(); foreach (var prop in element.EnumerateObject()) { - if (prop.NameEquals("audio"u8)) - { - if (prop.Value.ValueKind == JsonValueKind.Null) - { - continue; - } - audio = InternalChatCompletionMessageAudioChunk.DeserializeInternalChatCompletionMessageAudioChunk(prop.Value, options); - continue; - } if (prop.NameEquals("function_call"u8)) { if (prop.Value.ValueKind == JsonValueKind.Null) @@ -182,6 +173,15 @@ internal static InternalChatCompletionStreamResponseDelta DeserializeInternalCha DeserializeContentValue(prop, ref content); continue; } + if (prop.NameEquals("audio"u8)) + { + if (prop.Value.ValueKind == JsonValueKind.Null) + { + continue; + } + audio = ChatResponseAudio.DeserializeChatResponseAudio(prop.Value, options); + continue; + } if (true) { additionalBinaryDataProperties.Add(prop.Name, BinaryData.FromString(prop.Value.GetRawText())); @@ -189,12 +189,12 @@ internal static InternalChatCompletionStreamResponseDelta DeserializeInternalCha } // CUSTOM: Initialize Content collection property. return new InternalChatCompletionStreamResponseDelta( - audio, functionCall, toolCalls ?? new ChangeTrackingList(), refusal, role, content ?? new ChatMessageContent(), + audio, additionalBinaryDataProperties); } diff --git a/.dotnet/src/Generated/Models/InternalChatCompletionStreamResponseDelta.cs b/.dotnet/src/Generated/Models/InternalChatCompletionStreamResponseDelta.cs index 14dd3ca44..ac361d1d1 100644 --- a/.dotnet/src/Generated/Models/InternalChatCompletionStreamResponseDelta.cs +++ b/.dotnet/src/Generated/Models/InternalChatCompletionStreamResponseDelta.cs @@ -11,19 +11,17 @@ internal partial class InternalChatCompletionStreamResponseDelta { private protected IDictionary _additionalBinaryDataProperties; - internal InternalChatCompletionStreamResponseDelta(InternalChatCompletionMessageAudioChunk audio, StreamingChatFunctionCallUpdate functionCall, IReadOnlyList toolCalls, string refusal, Chat.ChatMessageRole? role, ChatMessageContent content, IDictionary additionalBinaryDataProperties) + internal InternalChatCompletionStreamResponseDelta(StreamingChatFunctionCallUpdate functionCall, IReadOnlyList toolCalls, string refusal, Chat.ChatMessageRole? role, ChatMessageContent content, ChatResponseAudio audio, IDictionary additionalBinaryDataProperties) { - Audio = audio; FunctionCall = functionCall; ToolCalls = toolCalls; Refusal = refusal; Role = role; Content = content; + Audio = audio; _additionalBinaryDataProperties = additionalBinaryDataProperties; } - public InternalChatCompletionMessageAudioChunk Audio { get; } - public StreamingChatFunctionCallUpdate FunctionCall { get; } public IReadOnlyList ToolCalls { get; } diff --git a/.dotnet/src/Generated/Models/InternalFineTuneChatCompletionRequestAssistantMessage.Serialization.cs b/.dotnet/src/Generated/Models/InternalFineTuneChatCompletionRequestAssistantMessage.Serialization.cs index 8b1a2c459..19cc9073b 100644 --- a/.dotnet/src/Generated/Models/InternalFineTuneChatCompletionRequestAssistantMessage.Serialization.cs +++ b/.dotnet/src/Generated/Models/InternalFineTuneChatCompletionRequestAssistantMessage.Serialization.cs @@ -57,7 +57,7 @@ internal static InternalFineTuneChatCompletionRequestAssistantMessage Deserializ string participantName = default; IList toolCalls = default; ChatFunctionCall functionCall = default; - InternalChatCompletionRequestAssistantMessageAudio audio = default; + ChatResponseAudioReference responseAudioReference = default; foreach (var prop in element.EnumerateObject()) { if (prop.NameEquals("content"u8)) @@ -113,10 +113,10 @@ internal static InternalFineTuneChatCompletionRequestAssistantMessage Deserializ { if (prop.Value.ValueKind == JsonValueKind.Null) { - audio = null; + responseAudioReference = null; continue; } - audio = InternalChatCompletionRequestAssistantMessageAudio.DeserializeInternalChatCompletionRequestAssistantMessageAudio(prop.Value, options); + responseAudioReference = ChatResponseAudioReference.DeserializeChatResponseAudioReference(prop.Value, options); continue; } if (true) @@ -133,7 +133,7 @@ internal static InternalFineTuneChatCompletionRequestAssistantMessage Deserializ participantName, toolCalls ?? new ChangeTrackingList(), functionCall, - audio); + responseAudioReference); } BinaryData IPersistableModel.Write(ModelReaderWriterOptions options) => PersistableModelWriteCore(options); diff --git a/.dotnet/src/Generated/Models/InternalFineTuneChatCompletionRequestAssistantMessage.cs b/.dotnet/src/Generated/Models/InternalFineTuneChatCompletionRequestAssistantMessage.cs index 85bdc8549..67114c758 100644 --- a/.dotnet/src/Generated/Models/InternalFineTuneChatCompletionRequestAssistantMessage.cs +++ b/.dotnet/src/Generated/Models/InternalFineTuneChatCompletionRequestAssistantMessage.cs @@ -14,7 +14,7 @@ public InternalFineTuneChatCompletionRequestAssistantMessage() { } - internal InternalFineTuneChatCompletionRequestAssistantMessage(ChatMessageContent content, Chat.ChatMessageRole role, IDictionary additionalBinaryDataProperties, string refusal, string participantName, IList toolCalls, ChatFunctionCall functionCall, InternalChatCompletionRequestAssistantMessageAudio audio) : base(content, role, additionalBinaryDataProperties, refusal, participantName, toolCalls, functionCall, audio) + internal InternalFineTuneChatCompletionRequestAssistantMessage(ChatMessageContent content, Chat.ChatMessageRole role, IDictionary additionalBinaryDataProperties, string refusal, string participantName, IList toolCalls, ChatFunctionCall functionCall, ChatResponseAudioReference responseAudioReference) : base(content, role, additionalBinaryDataProperties, refusal, participantName, toolCalls, functionCall, responseAudioReference) { } } diff --git a/.dotnet/src/Generated/OpenAIModelFactory.cs b/.dotnet/src/Generated/OpenAIModelFactory.cs index 77369458b..096e66a75 100644 --- a/.dotnet/src/Generated/OpenAIModelFactory.cs +++ b/.dotnet/src/Generated/OpenAIModelFactory.cs @@ -950,7 +950,7 @@ public static UserChatMessage UserChatMessage(ChatMessageContent content = defau return new UserChatMessage(content, Chat.ChatMessageRole.User, additionalBinaryDataProperties: null, participantName); } - public static AssistantChatMessage AssistantChatMessage(ChatMessageContent content = default, string refusal = default, string participantName = default, IEnumerable toolCalls = default, ChatFunctionCall functionCall = default, InternalChatCompletionRequestAssistantMessageAudio audio = default) + public static AssistantChatMessage AssistantChatMessage(ChatMessageContent content = default, string refusal = default, string participantName = default, IEnumerable toolCalls = default, ChatFunctionCall functionCall = default, ChatResponseAudioReference responseAudioReference = default) { toolCalls ??= new ChangeTrackingList(); @@ -962,7 +962,13 @@ public static AssistantChatMessage AssistantChatMessage(ChatMessageContent conte participantName, toolCalls?.ToList(), functionCall, - audio); + responseAudioReference); + } + + public static ChatResponseAudioReference ChatResponseAudioReference(string id = default) + { + + return new ChatResponseAudioReference(id, additionalBinaryDataProperties: null); } public static ChatToolCall ChatToolCall(string id = default, InternalChatCompletionMessageToolCallFunction function = default, Chat.ChatToolCallKind kind = default) @@ -1029,6 +1035,12 @@ public static ChatCompletion ChatCompletion(string id = default, string model = additionalBinaryDataProperties: null); } + public static ChatResponseAudio ChatResponseAudio(string id = default, DateTimeOffset expiresAt = default, BinaryData data = default, string transcript = default) + { + + return new ChatResponseAudio(id, expiresAt, data, transcript, additionalBinaryDataProperties: null); + } + public static ChatTokenLogProbabilityDetails ChatTokenLogProbabilityDetails(string token = default, float logProbability = default, ReadOnlyMemory? utf8Bytes = default, IEnumerable topLogProbabilities = default) { topLogProbabilities ??= new ChangeTrackingList(); @@ -1235,7 +1247,7 @@ public static ChatMessageContent ChatMessageContent() return new ChatMessageContent(additionalBinaryDataProperties: null); } - public static ChatMessageContentPart ChatMessageContentPart(Chat.ChatMessageContentPartKind kind = default, string text = default, InternalChatCompletionRequestMessageContentPartImageImageUrl imageUri = default, string refusal = default, InternalChatCompletionRequestMessageContentPartAudioInputAudio inputAudio = default, InternalChatCompletionResponseMessageAudio outputAudio = default, InternalChatCompletionMessageAudioChunk responseAudioUpdate = default, InternalChatCompletionRequestAssistantMessageAudio audioReference = default) + public static ChatMessageContentPart ChatMessageContentPart(Chat.ChatMessageContentPartKind kind = default, string text = default, InternalChatCompletionRequestMessageContentPartImageImageUrl imageUri = default, string refusal = default, InternalChatCompletionRequestMessageContentPartAudioInputAudio inputAudio = default) { return new ChatMessageContentPart( @@ -1244,9 +1256,6 @@ public static ChatMessageContentPart ChatMessageContentPart(Chat.ChatMessageCont imageUri, refusal, inputAudio, - outputAudio, - responseAudioUpdate, - audioReference, serializedAdditionalRawData: null); } diff --git a/.dotnet/tests/Chat/ChatSmokeTests.cs b/.dotnet/tests/Chat/ChatSmokeTests.cs index 618509ef2..3f2e90388 100644 --- a/.dotnet/tests/Chat/ChatSmokeTests.cs +++ b/.dotnet/tests/Chat/ChatSmokeTests.cs @@ -537,7 +537,7 @@ public void SerializeRefusalMessages() public void SerializeAudioThings() { // User audio input: wire-correlated ("real") content parts should cleanly serialize/deserialize - ChatMessageContentPart inputAudioContentPart = ChatMessageContentPart.CreateAudioPart( + ChatMessageContentPart inputAudioContentPart = ChatMessageContentPart.CreateInputAudioPart( BinaryData.FromBytes([0x4, 0x2]), ChatInputAudioFormat.Mp3); Assert.That(inputAudioContentPart, Is.Not.Null); @@ -546,16 +546,6 @@ public void SerializeAudioThings() ChatMessageContentPart deserializedInputAudioContentPart = ModelReaderWriter.Read(serializedInputAudioContentPart); Assert.That(deserializedInputAudioContentPart.AudioBytes.ToArray()[1], Is.EqualTo(0x2)); - // Synthetic content parts wrapping input audio references should throw - ChatMessageContentPart audioReferenceContentPart = ChatMessageContentPart.CreateAudioPart("audio-id"); - Assert.Throws(() => ModelReaderWriter.Write(audioReferenceContentPart)); - - // That same synthetic content part should serialize to the right place when added to a message - AssistantChatMessage messageWithAudioReference = new([audioReferenceContentPart]); - BinaryData serializedMessageWithAudioReference = ModelReaderWriter.Write(messageWithAudioReference); - Assert.That(serializedMessageWithAudioReference.ToString(), Does.Contain(@"""audio"":{""id"":""audio-id""}")); - Assert.That(serializedMessageWithAudioReference.ToString(), Does.Not.Contain(@"""content""")); - AssistantChatMessage message = ModelReaderWriter.Read(BinaryData.FromBytes(""" { "role": "assistant", @@ -564,13 +554,14 @@ public void SerializeAudioThings() } } """u8.ToArray())); - Assert.That(message.Content, Has.Count.EqualTo(1)); - Assert.That(message.Content[0].AudioCorrelationId, Is.EqualTo("audio_correlated_id_1234")); + Assert.That(message.Content, Has.Count.EqualTo(0)); + Assert.That(message.ResponseAudioReference, Is.Not.Null); + Assert.That(message.ResponseAudioReference.Id, Is.EqualTo("audio_correlated_id_1234")); string serializedMessage = ModelReaderWriter.Write(message).ToString(); Assert.That(serializedMessage, Does.Contain(@"""audio"":{""id"":""audio_correlated_id_1234""}")); AssistantChatMessage ordinaryTextAssistantMessage = new(["This was a message from the assistant"]); - ordinaryTextAssistantMessage.Content.Add(ChatMessageContentPart.CreateAudioPart("extra-audio-id")); + ordinaryTextAssistantMessage.ResponseAudioReference = new("extra-audio-id"); BinaryData serializedLateAudioMessage = ModelReaderWriter.Write(ordinaryTextAssistantMessage); Assert.That(serializedLateAudioMessage.ToString(), Does.Contain("was a message")); Assert.That(serializedLateAudioMessage.ToString(), Does.Contain("extra-audio-id")); @@ -621,14 +612,14 @@ public void SerializeAudioThings() """u8.ToArray()); ChatCompletion audioCompletion = ModelReaderWriter.Read(rawAudioResponse); Assert.That(audioCompletion, Is.Not.Null); - - // Synthetic response audio content parts (reprojecting internal choices[*].message.audio) should throw when - // attempting independent serialization - Assert.Throws(() => ModelReaderWriter.Write(audioCompletion.Content[0])); - + Assert.That(audioCompletion.Content, Has.Count.EqualTo(0)); + Assert.That(audioCompletion.ResponseAudio, Is.Not.Null); + Assert.That(audioCompletion.ResponseAudio.Id, Is.EqualTo("audio_6725224ac62481908ab55dc283289d87")); + Assert.That(audioCompletion.ResponseAudio.Data, Is.Not.Null); + Assert.That(audioCompletion.ResponseAudio.Transcript, Is.Not.Null.And.Not.Empty); + AssistantChatMessage audioHistoryMessage = new(audioCompletion); - BinaryData serializedAudioHistoryMessage = ModelReaderWriter.Write(audioHistoryMessage); - Console.WriteLine(serializedAudioHistoryMessage.ToString()); + Assert.That(audioHistoryMessage.ResponseAudioReference?.Id, Is.EqualTo(audioCompletion.ResponseAudio.Id)); } [Test] diff --git a/.dotnet/tests/Chat/ChatTests.cs b/.dotnet/tests/Chat/ChatTests.cs index 8c9ea8414..3a6da8060 100644 --- a/.dotnet/tests/Chat/ChatTests.cs +++ b/.dotnet/tests/Chat/ChatTests.cs @@ -373,12 +373,12 @@ public async Task ChatWithAudio() string helloWorldAudioPath = Path.Join("Assets", "audio_hello_world.mp3"); BinaryData helloWorldAudioBytes = BinaryData.FromBytes(File.ReadAllBytes(helloWorldAudioPath)); - ChatMessageContentPart helloWorldAudioContentPart = ChatMessageContentPart.CreateAudioPart( + ChatMessageContentPart helloWorldAudioContentPart = ChatMessageContentPart.CreateInputAudioPart( helloWorldAudioBytes, ChatInputAudioFormat.Mp3); - string whatsTheWeatherAudioPath = Path.Join("Assets", "whats_the_weather_pcm16_24khz_mono.wav"); + string whatsTheWeatherAudioPath = Path.Join("Assets", "realtime_whats_the_weather_pcm16_24khz_mono.wav"); BinaryData whatsTheWeatherAudioBytes = BinaryData.FromBytes(File.ReadAllBytes(whatsTheWeatherAudioPath)); - ChatMessageContentPart whatsTheWeatherAudioContentPart = ChatMessageContentPart.CreateAudioPart( + ChatMessageContentPart whatsTheWeatherAudioContentPart = ChatMessageContentPart.CreateInputAudioPart( whatsTheWeatherAudioBytes, ChatInputAudioFormat.Wav); @@ -391,40 +391,42 @@ public async Task ChatWithAudio() ChatCompletion completion = await client.CompleteChatAsync(messages, options); Assert.That(completion, Is.Not.Null); - Assert.That(completion.Content, Is.Not.Null); - Assert.That(completion.Content[0].Kind, Is.EqualTo(ChatMessageContentPartKind.Audio)); - Assert.That(completion.Content[0].AudioCorrelationId, Is.Not.Null.And.Not.Empty); - Assert.That(completion.Content[0].AudioBytes, Is.Not.Null); - Assert.That(completion.Content[0].AudioTranscript, Is.Not.Null.And.Not.Empty); + Assert.That(completion.Content, Has.Count.EqualTo(0)); + + ChatResponseAudio responseAudio = completion.ResponseAudio; + Assert.That(responseAudio, Is.Not.Null); + Assert.That(responseAudio.Id, Is.Not.Null.And.Not.Empty); + Assert.That(responseAudio.Data, Is.Not.Null); + Assert.That(responseAudio.Transcript, Is.Not.Null.And.Not.Empty); AssistantChatMessage audioHistoryMessage = ChatMessage.CreateAssistantMessage(completion); Assert.That(audioHistoryMessage, Is.InstanceOf()); - Assert.That(audioHistoryMessage.Content, Is.Not.Null.And.Not.Empty); - Assert.That(audioHistoryMessage.Content[0].Kind, Is.EqualTo(ChatMessageContentPartKind.Audio)); - Assert.That(audioHistoryMessage.Content[0].AudioCorrelationId, Is.EqualTo(completion.Content[0].AudioCorrelationId)); - Assert.That(audioHistoryMessage.Content[0].AudioBytes, Is.Null); + Assert.That(audioHistoryMessage.Content, Has.Count.EqualTo(0)); + + Assert.That(audioHistoryMessage.ResponseAudioReference?.Id, Is.EqualTo(completion.ResponseAudio.Id)); messages.Add(audioHistoryMessage); messages.Add( new UserChatMessage( [ "Please answer the following spoken question:", - ChatMessageContentPart.CreateAudioPart(whatsTheWeatherAudioBytes, ChatInputAudioFormat.Wav), + ChatMessageContentPart.CreateInputAudioPart(whatsTheWeatherAudioBytes, ChatInputAudioFormat.Wav), ])); string streamedCorrelationId = null; using MemoryStream responseAudioStream = new(); await foreach (StreamingChatCompletionUpdate update in client.CompleteChatStreamingAsync(messages, options)) { - Assert.That(update.ContentUpdate, Is.Not.Null); - if (update.ContentUpdate.Count > 0) + Assert.That(update.ContentUpdate, Has.Count.EqualTo(0)); + ChatResponseAudio responseAudioUpdate = update.ResponseAudio; + if (responseAudioUpdate is not null) { - if (!string.IsNullOrEmpty(update.ContentUpdate[0].AudioCorrelationId)) + if (responseAudioUpdate.Id is not null) { - Assert.That(streamedCorrelationId, Is.Null.Or.EqualTo(update.ContentUpdate[0].AudioCorrelationId)); - streamedCorrelationId = update.ContentUpdate[0].AudioCorrelationId; + Assert.That(streamedCorrelationId, Is.Null.Or.EqualTo(responseAudioUpdate.Id)); + streamedCorrelationId = responseAudioUpdate.Id; } - responseAudioStream.Write(update.ContentUpdate[0].AudioBytes); + responseAudioStream.Write(responseAudioUpdate.Data); } } Assert.That(streamedCorrelationId, Is.Not.Null.And.Not.Empty); diff --git a/.scripts/Edit-Serialization.ps1 b/.scripts/Edit-Serialization.ps1 index 18b296356..9e1be0160 100644 --- a/.scripts/Edit-Serialization.ps1 +++ b/.scripts/Edit-Serialization.ps1 @@ -45,23 +45,23 @@ Update-In-File-With-Retry ` -FilePath "$directory\InternalChatCompletionStreamResponseDelta.Serialization.cs" ` -SearchPatternLines @( "return new InternalChatCompletionStreamResponseDelta\(" - " audio," " functionCall," " toolCalls \?\? new ChangeTrackingList\(\)," " refusal," " role," " content," + " audio," " additionalBinaryDataProperties\);" ) ` -ReplacePatternLines @( "// CUSTOM: Initialize Content collection property." "return new InternalChatCompletionStreamResponseDelta(" - " audio," " functionCall," " toolCalls ?? new ChangeTrackingList()," " refusal," " role," " content ?? new ChatMessageContent()," + " audio," " additionalBinaryDataProperties);" ) ` -OutputIndentation 12 ` @@ -79,6 +79,34 @@ Update-In-File-With-Retry ` -OutputIndentation 12 ` -RequirePresence +Update-In-File-With-Retry ` + -FilePath "$directory\AssistantChatMessage.Serialization.cs" ` + -SearchPatternLines @( + "return new AssistantChatMessage\(" + " content," + " role," + " additionalBinaryDataProperties," + " refusal," + " participantName," + " toolCalls \?\? new ChangeTrackingList\(\)," + " functionCall," + " responseAudioReference\);" + ) ` + -ReplacePatternLines @( + "// CUSTOM: Initialize Content collection property." + "return new AssistantChatMessage(" + " content ?? new ChatMessageContent()," + " role," + " additionalBinaryDataProperties," + " refusal," + " participantName," + " toolCalls ?? new ChangeTrackingList()," + " functionCall," + " responseAudioReference);" + ) ` + -OutputIndentation 12 ` + -RequirePresence + Update-In-File-With-Retry ` -FilePath "$directory\FunctionChatMessage.Serialization.cs" ` -SearchPatternLines @( @@ -150,7 +178,7 @@ Update-In-File-With-Retry ` " participantName," " toolCalls \?\? new ChangeTrackingList\(\)," " functionCall," - " audio\);" + " responseAudioReference\);" ) ` -ReplacePatternLines @( "// CUSTOM: Initialize Content collection property." @@ -162,7 +190,7 @@ Update-In-File-With-Retry ` " participantName," " toolCalls ?? new ChangeTrackingList()," " functionCall," - " audio);" + " responseAudioReference);" ) ` -OutputIndentation 12 ` -RequirePresence