From 3864226412987009cd0752f9ecbebe03a38e9384 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Wed, 2 Apr 2025 09:57:47 -0400 Subject: [PATCH 1/2] Use ErrorContent in OpenAIResponseChatClient --- .../Contents/ErrorContent.cs | 22 ++++++++++-------- .../OpenAIResponseChatClient.cs | 23 +++++++++++++++++++ .../Contents/ErrorContentTests.cs | 13 +++++++++++ 3 files changed, 48 insertions(+), 10 deletions(-) diff --git a/src/Libraries/Microsoft.Extensions.AI.Abstractions/Contents/ErrorContent.cs b/src/Libraries/Microsoft.Extensions.AI.Abstractions/Contents/ErrorContent.cs index ceca3002f88..b852bdb076f 100644 --- a/src/Libraries/Microsoft.Extensions.AI.Abstractions/Contents/ErrorContent.cs +++ b/src/Libraries/Microsoft.Extensions.AI.Abstractions/Contents/ErrorContent.cs @@ -3,7 +3,6 @@ using System.Diagnostics; using System.Text.Json.Serialization; -using Microsoft.Shared.Diagnostics; namespace Microsoft.Extensions.AI; @@ -18,31 +17,34 @@ public class ErrorContent : AIContent /// The error message. private string _message; - /// Initializes a new instance of the class with the specified message. - /// The message to store in this content. + /// Initializes a new instance of the class with the specified error message. + /// The error message to store in this content. [JsonConstructor] public ErrorContent(string message) { - _message = Throw.IfNull(message); + // The message shouldn't be null. However, if it's being constructed from another system that may end up + // producing a null message, we want to be lenient and not produce an exception while trying to store + // information about an error. So, rather than throwing if message is null, we just normalize to empty. + _message = message ?? string.Empty; } /// Gets or sets the error message. public string Message { get => _message; - set => _message = Throw.IfNull(value); + set => _message = value ?? string.Empty; } - /// Gets or sets the error code. + /// Gets or sets an error code associated with the error. public string? ErrorCode { get; set; } - /// Gets or sets the error details. + /// Gets or sets additional details about the error. public string? Details { get; set; } /// Gets a string representing this instance to display in the debugger. [DebuggerBrowsable(DebuggerBrowsableState.Never)] private string DebuggerDisplay => - $"Error = {Message}" + - (ErrorCode is not null ? $" ({ErrorCode})" : string.Empty) + - (Details is not null ? $" - {Details}" : string.Empty); + $"Error = \"{Message}\"" + + (!string.IsNullOrWhiteSpace(ErrorCode) ? $" ({ErrorCode})" : string.Empty) + + (!string.IsNullOrWhiteSpace(Details) ? $" - \"{Details}\"" : string.Empty); } diff --git a/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIResponseChatClient.cs b/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIResponseChatClient.cs index 566aac8fa63..70768f07caa 100644 --- a/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIResponseChatClient.cs +++ b/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIResponseChatClient.cs @@ -132,6 +132,11 @@ public async Task GetResponseAsync( break; } } + + if (openAIResponse.Error is { } error) + { + message.Contents.Add(new ErrorContent(error.Message) { ErrorCode = error.Code }); + } } return response; @@ -246,6 +251,24 @@ public async IAsyncEnumerable GetStreamingResponseAsync( break; } + + case StreamingResponseErrorUpdate errorUpdate: + yield return new ChatResponseUpdate + { + CreatedAt = createdAt, + MessageId = lastMessageId, + ModelId = modelId, + ResponseId = responseId, + Contents = + [ + new ErrorContent(errorUpdate.Message) + { + ErrorCode = errorUpdate.Code, + Details = errorUpdate.Param, + } + ], + }; + break; } } } diff --git a/test/Libraries/Microsoft.Extensions.AI.Abstractions.Tests/Contents/ErrorContentTests.cs b/test/Libraries/Microsoft.Extensions.AI.Abstractions.Tests/Contents/ErrorContentTests.cs index 2564f6bc2c9..8744636b6ec 100644 --- a/test/Libraries/Microsoft.Extensions.AI.Abstractions.Tests/Contents/ErrorContentTests.cs +++ b/test/Libraries/Microsoft.Extensions.AI.Abstractions.Tests/Contents/ErrorContentTests.cs @@ -8,6 +8,19 @@ namespace Microsoft.Extensions.AI; public class ErrorContentTests { + [Fact] + public void Constructor_NormalizesNullToEmpty() + { + ErrorContent content = new(null!); + Assert.Empty(content.Message); + + content.Message = "test"; + Assert.Equal("test", content.Message); + + content.Message = null!; + Assert.Empty(content.Message); + } + [Fact] public void Constructor_ShouldInitializeProperties() { From bfd49cf09820b03043340a01a1da89c899b512f0 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Wed, 2 Apr 2025 11:41:10 -0400 Subject: [PATCH 2/2] Address feedback --- .../Contents/ErrorContent.cs | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/src/Libraries/Microsoft.Extensions.AI.Abstractions/Contents/ErrorContent.cs b/src/Libraries/Microsoft.Extensions.AI.Abstractions/Contents/ErrorContent.cs index b852bdb076f..4588531262b 100644 --- a/src/Libraries/Microsoft.Extensions.AI.Abstractions/Contents/ErrorContent.cs +++ b/src/Libraries/Microsoft.Extensions.AI.Abstractions/Contents/ErrorContent.cs @@ -2,7 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; -using System.Text.Json.Serialization; +using System.Diagnostics.CodeAnalysis; namespace Microsoft.Extensions.AI; @@ -15,24 +15,21 @@ namespace Microsoft.Extensions.AI; public class ErrorContent : AIContent { /// The error message. - private string _message; + private string? _message; /// Initializes a new instance of the class with the specified error message. /// The error message to store in this content. - [JsonConstructor] - public ErrorContent(string message) + public ErrorContent(string? message) { - // The message shouldn't be null. However, if it's being constructed from another system that may end up - // producing a null message, we want to be lenient and not produce an exception while trying to store - // information about an error. So, rather than throwing if message is null, we just normalize to empty. - _message = message ?? string.Empty; + _message = message; } /// Gets or sets the error message. + [AllowNull] public string Message { - get => _message; - set => _message = value ?? string.Empty; + get => _message ?? string.Empty; + set => _message = value; } /// Gets or sets an error code associated with the error.