diff --git a/api/OpenAI.net8.0.cs b/api/OpenAI.net8.0.cs index 4b5086252..d9e42a0b3 100644 --- a/api/OpenAI.net8.0.cs +++ b/api/OpenAI.net8.0.cs @@ -1466,6 +1466,14 @@ public class ChatClient { public virtual AsyncCollectionResult GetChatCompletionsAsync(ChatCompletionCollectionOptions options = null, CancellationToken cancellationToken = default); [Experimental("OPENAI001")] public virtual AsyncCollectionResult GetChatCompletionsAsync(string after, int? limit, string order, IDictionary metadata, string model, RequestOptions options); + [Experimental("OPENAI001")] + public virtual ClientResult UpdateChatCompletion(string completionId, BinaryContent content, RequestOptions options = null); + [Experimental("OPENAI001")] + public virtual ClientResult UpdateChatCompletion(string completionId, IDictionary metadata, CancellationToken cancellationToken = default); + [Experimental("OPENAI001")] + public virtual Task UpdateChatCompletionAsync(string completionId, BinaryContent content, RequestOptions options = null); + [Experimental("OPENAI001")] + public virtual Task> UpdateChatCompletionAsync(string completionId, IDictionary metadata, CancellationToken cancellationToken = default); } public class ChatCompletion : IJsonModel, IPersistableModel { [Experimental("OPENAI001")] @@ -1534,7 +1542,7 @@ public class ChatCompletionDeletionResult : IJsonModel, IPersistableModel { public string AfterId { get; set; } - public ChatCompletionCollectionOrder? Order { get; set; } + public ChatCompletionMessageCollectionOrder? Order { get; set; } public int? PageSizeLimit { get; set; } protected virtual ChatCompletionMessageCollectionOptions JsonModelCreateCore(ref Utf8JsonReader reader, ModelReaderWriterOptions options); protected virtual void JsonModelWriteCore(Utf8JsonWriter writer, ModelReaderWriterOptions options); @@ -1542,11 +1550,26 @@ public class ChatCompletionMessageCollectionOptions : IJsonModel { + public ChatCompletionMessageCollectionOrder(string value); + public static ChatCompletionMessageCollectionOrder Ascending { get; } + public static ChatCompletionMessageCollectionOrder Descending { get; } + public readonly bool Equals(ChatCompletionMessageCollectionOrder other); + [EditorBrowsable(EditorBrowsableState.Never)] + public override readonly bool Equals(object obj); + [EditorBrowsable(EditorBrowsableState.Never)] + public override readonly int GetHashCode(); + public static bool operator ==(ChatCompletionMessageCollectionOrder left, ChatCompletionMessageCollectionOrder right); + public static implicit operator ChatCompletionMessageCollectionOrder(string value); + public static bool operator !=(ChatCompletionMessageCollectionOrder left, ChatCompletionMessageCollectionOrder right); + public override readonly string ToString(); + } + [Experimental("OPENAI001")] public class ChatCompletionMessageListDatum : IJsonModel, IPersistableModel { - public IList Annotations { get; } - public ChatOutputAudio Audio { get; } + public IReadOnlyList Annotations { get; } public string Content { get; } public string Id { get; } + public ChatOutputAudio OutputAudio { get; } public string Refusal { get; } public IReadOnlyList ToolCalls { get; } protected virtual ChatCompletionMessageListDatum JsonModelCreateCore(ref Utf8JsonReader reader, ModelReaderWriterOptions options); diff --git a/api/OpenAI.netstandard2.0.cs b/api/OpenAI.netstandard2.0.cs index 2a7f95262..2306f43be 100644 --- a/api/OpenAI.netstandard2.0.cs +++ b/api/OpenAI.netstandard2.0.cs @@ -1305,6 +1305,10 @@ public class ChatClient { public virtual CollectionResult GetChatCompletions(string after, int? limit, string order, IDictionary metadata, string model, RequestOptions options); public virtual AsyncCollectionResult GetChatCompletionsAsync(ChatCompletionCollectionOptions options = null, CancellationToken cancellationToken = default); public virtual AsyncCollectionResult GetChatCompletionsAsync(string after, int? limit, string order, IDictionary metadata, string model, RequestOptions options); + public virtual ClientResult UpdateChatCompletion(string completionId, BinaryContent content, RequestOptions options = null); + public virtual ClientResult UpdateChatCompletion(string completionId, IDictionary metadata, CancellationToken cancellationToken = default); + public virtual Task UpdateChatCompletionAsync(string completionId, BinaryContent content, RequestOptions options = null); + public virtual Task> UpdateChatCompletionAsync(string completionId, IDictionary metadata, CancellationToken cancellationToken = default); } public class ChatCompletion : IJsonModel, IPersistableModel { public IReadOnlyList Annotations { get; } @@ -1363,18 +1367,32 @@ public class ChatCompletionDeletionResult : IJsonModel, IPersistableModel { public string AfterId { get; set; } - public ChatCompletionCollectionOrder? Order { get; set; } + public ChatCompletionMessageCollectionOrder? Order { get; set; } public int? PageSizeLimit { get; set; } protected virtual ChatCompletionMessageCollectionOptions JsonModelCreateCore(ref Utf8JsonReader reader, ModelReaderWriterOptions options); protected virtual void JsonModelWriteCore(Utf8JsonWriter writer, ModelReaderWriterOptions options); protected virtual ChatCompletionMessageCollectionOptions PersistableModelCreateCore(BinaryData data, ModelReaderWriterOptions options); protected virtual BinaryData PersistableModelWriteCore(ModelReaderWriterOptions options); } + public readonly partial struct ChatCompletionMessageCollectionOrder : IEquatable { + public ChatCompletionMessageCollectionOrder(string value); + public static ChatCompletionMessageCollectionOrder Ascending { get; } + public static ChatCompletionMessageCollectionOrder Descending { get; } + public readonly bool Equals(ChatCompletionMessageCollectionOrder other); + [EditorBrowsable(EditorBrowsableState.Never)] + public override readonly bool Equals(object obj); + [EditorBrowsable(EditorBrowsableState.Never)] + public override readonly int GetHashCode(); + public static bool operator ==(ChatCompletionMessageCollectionOrder left, ChatCompletionMessageCollectionOrder right); + public static implicit operator ChatCompletionMessageCollectionOrder(string value); + public static bool operator !=(ChatCompletionMessageCollectionOrder left, ChatCompletionMessageCollectionOrder right); + public override readonly string ToString(); + } public class ChatCompletionMessageListDatum : IJsonModel, IPersistableModel { - public IList Annotations { get; } - public ChatOutputAudio Audio { get; } + public IReadOnlyList Annotations { get; } public string Content { get; } public string Id { get; } + public ChatOutputAudio OutputAudio { get; } public string Refusal { get; } public IReadOnlyList ToolCalls { get; } protected virtual ChatCompletionMessageListDatum JsonModelCreateCore(ref Utf8JsonReader reader, ModelReaderWriterOptions options); diff --git a/src/Custom/Chat/ChatClient.Protocol.cs b/src/Custom/Chat/ChatClient.Protocol.cs deleted file mode 100644 index b5d8e5d72..000000000 --- a/src/Custom/Chat/ChatClient.Protocol.cs +++ /dev/null @@ -1,53 +0,0 @@ -using System.ClientModel; -using System.ClientModel.Primitives; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.Threading.Tasks; - -namespace OpenAI.Chat; - -/// The service client for the OpenAI Chat Completions endpoint. -[CodeGenSuppress("UpdateChatCompletionAsync", typeof(string), typeof(BinaryContent), typeof(RequestOptions))] -[CodeGenSuppress("UpdateChatCompletion", typeof(string), typeof(BinaryContent), typeof(RequestOptions))] -public partial class ChatClient -{ - // CUSTOM: Added Experimental attribute. - [Experimental("OPENAI001")] - public virtual ClientResult GetChatCompletion(string completionId, RequestOptions options) - { - Argument.AssertNotNullOrEmpty(completionId, nameof(completionId)); - - using PipelineMessage message = CreateGetChatCompletionRequest(completionId, options); - return ClientResult.FromResponse(Pipeline.ProcessMessage(message, options)); - } - - // CUSTOM: Added Experimental attribute. - [Experimental("OPENAI001")] - public virtual async Task GetChatCompletionAsync(string completionId, RequestOptions options) - { - Argument.AssertNotNullOrEmpty(completionId, nameof(completionId)); - - using PipelineMessage message = CreateGetChatCompletionRequest(completionId, options); - return ClientResult.FromResponse(await Pipeline.ProcessMessageAsync(message, options).ConfigureAwait(false)); - } - - // CUSTOM: Added Experimental attribute. - [Experimental("OPENAI001")] - public virtual ClientResult DeleteChatCompletion(string completionId, RequestOptions options) - { - Argument.AssertNotNullOrEmpty(completionId, nameof(completionId)); - - using PipelineMessage message = CreateDeleteChatCompletionRequest(completionId, options); - return ClientResult.FromResponse(Pipeline.ProcessMessage(message, options)); - } - - // CUSTOM: Added Experimental attribute. - [Experimental("OPENAI001")] - public virtual async Task DeleteChatCompletionAsync(string completionId, RequestOptions options) - { - Argument.AssertNotNullOrEmpty(completionId, nameof(completionId)); - - using PipelineMessage message = CreateDeleteChatCompletionRequest(completionId, options); - return ClientResult.FromResponse(await Pipeline.ProcessMessageAsync(message, options).ConfigureAwait(false)); - } -} diff --git a/src/Custom/Chat/ChatClient.cs b/src/Custom/Chat/ChatClient.cs index c5f294c2f..d488af943 100644 --- a/src/Custom/Chat/ChatClient.cs +++ b/src/Custom/Chat/ChatClient.cs @@ -1,4 +1,3 @@ -using OpenAI.Evals; using OpenAI.Telemetry; using System; using System.ClientModel; @@ -20,8 +19,6 @@ namespace OpenAI.Chat; [CodeGenSuppress("ChatClient", typeof(ClientPipeline), typeof(Uri))] [CodeGenSuppress("CompleteChat", typeof(ChatCompletionOptions), typeof(CancellationToken))] [CodeGenSuppress("CompleteChatAsync", typeof(ChatCompletionOptions), typeof(CancellationToken))] -[CodeGenSuppress("UpdateChatCompletion", typeof(string), typeof(IDictionary), typeof(CancellationToken))] -[CodeGenSuppress("UpdateChatCompletionAsync", typeof(string), typeof(IDictionary), typeof(CancellationToken))] public partial class ChatClient { private readonly string _model; @@ -307,6 +304,32 @@ public virtual ClientResult GetChatCompletion(string completionI return ClientResult.FromValue(ChatCompletion.FromClientResult(result), result.GetRawResponse()); } + // CUSTOM: + // - Call FromClientResult. + [Experimental("OPENAI001")] + public virtual ClientResult UpdateChatCompletion(string completionId, IDictionary metadata, CancellationToken cancellationToken = default) + { + Argument.AssertNotNullOrEmpty(completionId, nameof(completionId)); + Argument.AssertNotNull(metadata, nameof(metadata)); + + InternalUpdateChatCompletionRequest spreadModel = new InternalUpdateChatCompletionRequest(metadata, null); + ClientResult result = this.UpdateChatCompletion(completionId, spreadModel, cancellationToken.CanBeCanceled ? new RequestOptions { CancellationToken = cancellationToken } : null); + return ClientResult.FromValue(ChatCompletion.FromClientResult(result), result.GetRawResponse()); + } + + // CUSTOM: + // - Call FromClientResult. + [Experimental("OPENAI001")] + public virtual async Task> UpdateChatCompletionAsync(string completionId, IDictionary metadata, CancellationToken cancellationToken = default) + { + Argument.AssertNotNullOrEmpty(completionId, nameof(completionId)); + Argument.AssertNotNull(metadata, nameof(metadata)); + + InternalUpdateChatCompletionRequest spreadModel = new InternalUpdateChatCompletionRequest(metadata, null); + ClientResult result = await this.UpdateChatCompletionAsync(completionId, spreadModel, cancellationToken.CanBeCanceled ? new RequestOptions { CancellationToken = cancellationToken } : null).ConfigureAwait(false); + return ClientResult.FromValue(ChatCompletion.FromClientResult(result), result.GetRawResponse()); + } + // CUSTOM: // - Added Experimental attribute. // - Call FromClientResult. diff --git a/src/Custom/Chat/Internal/GeneratorStubs.cs b/src/Custom/Chat/Internal/GeneratorStubs.cs index 9e5acc63e..9730bd6e2 100644 --- a/src/Custom/Chat/Internal/GeneratorStubs.cs +++ b/src/Custom/Chat/Internal/GeneratorStubs.cs @@ -1,4 +1,4 @@ -using System.Runtime.InteropServices; +using System.ClientModel; namespace OpenAI.Chat; @@ -50,6 +50,19 @@ internal partial class InternalChatCompletionRequestMessageContentPartRefusal { [CodeGenType("CreateChatCompletionRequestModel")] internal readonly partial struct InternalCreateChatCompletionRequestModel { } +[CodeGenType("UpdateChatCompletionRequest")] +internal partial class InternalUpdateChatCompletionRequest +{ + public static implicit operator BinaryContent(InternalUpdateChatCompletionRequest internalUpdateChatCompletionRequest) + { + if (internalUpdateChatCompletionRequest == null) + { + return null; + } + return BinaryContent.Create(internalUpdateChatCompletionRequest, ModelSerializationExtensions.WireOptions); + } +} + [CodeGenType("CreateChatCompletionRequestToolChoice")] internal readonly partial struct InternalCreateChatCompletionRequestToolChoice { } diff --git a/src/Generated/ChatClient.cs b/src/Generated/ChatClient.cs index d4d4f5ca5..dbd262bee 100644 --- a/src/Generated/ChatClient.cs +++ b/src/Generated/ChatClient.cs @@ -91,6 +91,62 @@ public virtual async Task CompleteChatAsync(BinaryContent content, return ClientResult.FromResponse(await Pipeline.ProcessMessageAsync(message, options).ConfigureAwait(false)); } + [Experimental("OPENAI001")] + public virtual ClientResult GetChatCompletion(string completionId, RequestOptions options) + { + Argument.AssertNotNullOrEmpty(completionId, nameof(completionId)); + + using PipelineMessage message = CreateGetChatCompletionRequest(completionId, options); + return ClientResult.FromResponse(Pipeline.ProcessMessage(message, options)); + } + + [Experimental("OPENAI001")] + public virtual async Task GetChatCompletionAsync(string completionId, RequestOptions options) + { + Argument.AssertNotNullOrEmpty(completionId, nameof(completionId)); + + using PipelineMessage message = CreateGetChatCompletionRequest(completionId, options); + return ClientResult.FromResponse(await Pipeline.ProcessMessageAsync(message, options).ConfigureAwait(false)); + } + + [Experimental("OPENAI001")] + public virtual ClientResult UpdateChatCompletion(string completionId, BinaryContent content, RequestOptions options = null) + { + Argument.AssertNotNullOrEmpty(completionId, nameof(completionId)); + Argument.AssertNotNull(content, nameof(content)); + + using PipelineMessage message = CreateUpdateChatCompletionRequest(completionId, content, options); + return ClientResult.FromResponse(Pipeline.ProcessMessage(message, options)); + } + + [Experimental("OPENAI001")] + public virtual async Task UpdateChatCompletionAsync(string completionId, BinaryContent content, RequestOptions options = null) + { + Argument.AssertNotNullOrEmpty(completionId, nameof(completionId)); + Argument.AssertNotNull(content, nameof(content)); + + using PipelineMessage message = CreateUpdateChatCompletionRequest(completionId, content, options); + return ClientResult.FromResponse(await Pipeline.ProcessMessageAsync(message, options).ConfigureAwait(false)); + } + + [Experimental("OPENAI001")] + public virtual ClientResult DeleteChatCompletion(string completionId, RequestOptions options) + { + Argument.AssertNotNullOrEmpty(completionId, nameof(completionId)); + + using PipelineMessage message = CreateDeleteChatCompletionRequest(completionId, options); + return ClientResult.FromResponse(Pipeline.ProcessMessage(message, options)); + } + + [Experimental("OPENAI001")] + public virtual async Task DeleteChatCompletionAsync(string completionId, RequestOptions options) + { + Argument.AssertNotNullOrEmpty(completionId, nameof(completionId)); + + using PipelineMessage message = CreateDeleteChatCompletionRequest(completionId, options); + return ClientResult.FromResponse(await Pipeline.ProcessMessageAsync(message, options).ConfigureAwait(false)); + } + [Experimental("OPENAI001")] public virtual CollectionResult GetChatCompletionMessages(string completionId, string after, int? limit, string order, RequestOptions options) { diff --git a/src/Generated/Models/Chat/InternalUpdateChatCompletionRequest.Serialization.cs b/src/Generated/Models/Chat/InternalUpdateChatCompletionRequest.Serialization.cs new file mode 100644 index 000000000..edfbf01a9 --- /dev/null +++ b/src/Generated/Models/Chat/InternalUpdateChatCompletionRequest.Serialization.cs @@ -0,0 +1,150 @@ +// + +#nullable disable + +using System; +using System.ClientModel.Primitives; +using System.Collections.Generic; +using System.Text.Json; +using OpenAI; + +namespace OpenAI.Chat +{ + internal partial class InternalUpdateChatCompletionRequest : IJsonModel + { + internal InternalUpdateChatCompletionRequest() : this(null, null) + { + } + + 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(InternalUpdateChatCompletionRequest)} does not support writing '{format}' format."); + } + if (_additionalBinaryDataProperties?.ContainsKey("metadata") != true) + { + writer.WritePropertyName("metadata"u8); + writer.WriteStartObject(); + foreach (var item in Metadata) + { + writer.WritePropertyName(item.Key); + if (item.Value == null) + { + writer.WriteNullValue(); + continue; + } + writer.WriteStringValue(item.Value); + } + writer.WriteEndObject(); + } + // Plugin customization: remove options.Format != "W" check + if (_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 + } + } + } + + InternalUpdateChatCompletionRequest IJsonModel.Create(ref Utf8JsonReader reader, ModelReaderWriterOptions options) => JsonModelCreateCore(ref reader, options); + + protected virtual InternalUpdateChatCompletionRequest 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(InternalUpdateChatCompletionRequest)} does not support reading '{format}' format."); + } + using JsonDocument document = JsonDocument.ParseValue(ref reader); + return DeserializeInternalUpdateChatCompletionRequest(document.RootElement, options); + } + + internal static InternalUpdateChatCompletionRequest DeserializeInternalUpdateChatCompletionRequest(JsonElement element, ModelReaderWriterOptions options) + { + if (element.ValueKind == JsonValueKind.Null) + { + return null; + } + IDictionary metadata = default; + IDictionary additionalBinaryDataProperties = new ChangeTrackingDictionary(); + foreach (var prop in element.EnumerateObject()) + { + if (prop.NameEquals("metadata"u8)) + { + Dictionary dictionary = new Dictionary(); + foreach (var prop0 in prop.Value.EnumerateObject()) + { + if (prop0.Value.ValueKind == JsonValueKind.Null) + { + dictionary.Add(prop0.Name, null); + } + else + { + dictionary.Add(prop0.Name, prop0.Value.GetString()); + } + } + metadata = dictionary; + continue; + } + // Plugin customization: remove options.Format != "W" check + additionalBinaryDataProperties.Add(prop.Name, BinaryData.FromString(prop.Value.GetRawText())); + } + return new InternalUpdateChatCompletionRequest(metadata, 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, OpenAIContext.Default); + default: + throw new FormatException($"The model {nameof(InternalUpdateChatCompletionRequest)} does not support writing '{options.Format}' format."); + } + } + + InternalUpdateChatCompletionRequest IPersistableModel.Create(BinaryData data, ModelReaderWriterOptions options) => PersistableModelCreateCore(data, options); + + protected virtual InternalUpdateChatCompletionRequest 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 DeserializeInternalUpdateChatCompletionRequest(document.RootElement, options); + } + default: + throw new FormatException($"The model {nameof(InternalUpdateChatCompletionRequest)} does not support reading '{options.Format}' format."); + } + } + + string IPersistableModel.GetFormatFromOptions(ModelReaderWriterOptions options) => "J"; + } +} diff --git a/src/Generated/Models/Chat/InternalUpdateChatCompletionRequest.cs b/src/Generated/Models/Chat/InternalUpdateChatCompletionRequest.cs new file mode 100644 index 000000000..120890eb5 --- /dev/null +++ b/src/Generated/Models/Chat/InternalUpdateChatCompletionRequest.cs @@ -0,0 +1,36 @@ +// + +#nullable disable + +using System; +using System.Collections.Generic; +using OpenAI; + +namespace OpenAI.Chat +{ + internal partial class InternalUpdateChatCompletionRequest + { + private protected IDictionary _additionalBinaryDataProperties; + + internal InternalUpdateChatCompletionRequest(IDictionary metadata) + { + // Plugin customization: ensure initialization of collections + Metadata = metadata ?? new ChangeTrackingDictionary(); + } + + internal InternalUpdateChatCompletionRequest(IDictionary metadata, IDictionary additionalBinaryDataProperties) + { + // Plugin customization: ensure initialization of collections + Metadata = metadata ?? new ChangeTrackingDictionary(); + _additionalBinaryDataProperties = additionalBinaryDataProperties; + } + + public IDictionary Metadata { get; } + + internal IDictionary SerializedAdditionalRawData + { + get => _additionalBinaryDataProperties; + set => _additionalBinaryDataProperties = value; + } + } +} diff --git a/tests/Chat/ChatTests.cs b/tests/Chat/ChatTests.cs index 40ba407a8..6bb80142c 100644 --- a/tests/Chat/ChatTests.cs +++ b/tests/Chat/ChatTests.cs @@ -1543,6 +1543,46 @@ await RetryWithExponentialBackoffAsync(async () => }); } + [Test] + public async Task UpdateChatCompletionWorks() + { + ChatClient client = GetTestClient(); + + var testMetadataKey = $"test_key_{Guid.NewGuid():N}"; + var initialOptions = new ChatCompletionOptions + { + StoredOutputEnabled = true, + Metadata = { [testMetadataKey] = "initial_value" } + }; + + ChatCompletion chatCompletion = await client.CompleteChatAsync( + [new UserChatMessage("Say `this is a test`.")], + initialOptions); + + await Task.Delay(5000); + + var newMetadata = new Dictionary + { + [testMetadataKey] = "updated_value", + ["updated_by"] = "unit_test" + }; + + ChatCompletion updated = await client.UpdateChatCompletionAsync(chatCompletion.Id, newMetadata); + + Assert.That(updated, Is.Not.Null); + Assert.That(updated.Id, Is.EqualTo(chatCompletion.Id)); + + ChatCompletionDeletionResult deletionResult = await client.DeleteChatCompletionAsync(chatCompletion.Id); + Assert.That(deletionResult.Deleted, Is.True); + + await Task.Delay(5000); + + Assert.ThrowsAsync(async () => + { + _ = await client.GetChatCompletionAsync(chatCompletion.Id); + }); + } + private List FileIdsToDelete = []; private void Validate(T item) {