Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve OpenAI API error logging #185

Merged
merged 3 commits into from
May 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions src/Lib.Services/Services/OpenAiService/OpenAiService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -114,10 +114,19 @@ public OpenAiService(IOptions<OpenAiServiceOptions> options, IHttpClientFactory
}
catch (Exception ex)
{
_logger.LogOpenAiApiServiceFailure(ex);
string errorResponseContent = await responseMessage.Content.ReadAsStringAsync();

OpenAiApiException openAiApiException = new(
message: $"An error occurred while interacting with the OpenAI API:\n{errorResponseContent}",
errorResponseContent: errorResponseContent,
httpStatusCode: responseMessage.StatusCode,
innerException: ex
);

_logger.LogOpenAiApiServiceFailure(openAiApiException);
activity?.SetStatus(ActivityStatusCode.Error);

throw;
throw openAiApiException;
}

Stream responseStream = await responseMessage.Content.ReadAsStreamAsync();
Expand Down
75 changes: 75 additions & 0 deletions src/Lib/Exceptions/OpenAi/OpenAiApiException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
using System.Net;

using MuzakBot.Lib.Models.OpenAi;

namespace MuzakBot.Lib;

/// <summary>
/// Exception thrown when an error occurs while interacting with the OpenAI API.
/// </summary>
public class OpenAiApiException : Exception
{
/// <summary>
/// Initializes a new instance of the <see cref="OpenAiApiException"/> class.
/// </summary>
/// <param name="message">The message that describes the error.</param>
/// <param name="errorResponseContent">The content of the error response from OpenAI.</param>
/// <param name="httpStatusCode">The HTTP status code of the response.</param>
public OpenAiApiException(string message, string errorResponseContent, HttpStatusCode httpStatusCode) : base(message)
{
ApiErrorResponse = JsonSerializer.Deserialize(
json: errorResponseContent,
jsonTypeInfo: OpenAiJsonContext.Default.OpenAiErrorResponse
);

HttpStatusCode = httpStatusCode;
HttpStatusCodeMessage = GetCommonHttpStatusCodeMessage(httpStatusCode);
}

/// <summary>
/// Initializes a new instance of the <see cref="OpenAiApiException"/> class.
/// </summary>
/// <param name="message">The message that describes the error.</param>
/// <param name="errorResponseContent">The content of the error response from OpenAI.</param>
/// <param name="httpStatusCode">The HTTP status code of the response.</param>
/// <param name="innerException">The exception that is the cause of the current exception.</param>
public OpenAiApiException(string message, string errorResponseContent, HttpStatusCode httpStatusCode, Exception innerException) : base(message, innerException)
{
ApiErrorResponse = JsonSerializer.Deserialize(
json: errorResponseContent,
jsonTypeInfo: OpenAiJsonContext.Default.OpenAiErrorResponse
);

HttpStatusCode = httpStatusCode;
HttpStatusCodeMessage = GetCommonHttpStatusCodeMessage(httpStatusCode);
}

/// <summary>
/// The error response from OpenAI.
/// </summary>
public OpenAiErrorResponse? ApiErrorResponse { get; set; }

/// <summary>
/// The HTTP status code of the response.
/// </summary>
public HttpStatusCode HttpStatusCode { get; set; }

/// <summary>
/// A message that describes the HTTP status code.
/// </summary>
public string HttpStatusCodeMessage { get; set; }

private static string GetCommonHttpStatusCodeMessage(HttpStatusCode httpStatusCode)
{
return httpStatusCode switch
{
HttpStatusCode.BadRequest => "Your request was malformed or missing some required parameters, such as a token or an input.",
HttpStatusCode.InternalServerError => "An error occurred on the OpenAI server. Please try again later.",
HttpStatusCode.NotFound => "The requested resource does not exist.",
HttpStatusCode.Unauthorized => "The provided API key does not have permission to access the requested resource.",
HttpStatusCode.TooManyRequests => "You have either exceeded the rate limit for the OpenAI API or you have run out of credits. Please try again later.",
HttpStatusCode.ServiceUnavailable => "OpenAI's API is experiencing high traffic. Please try again later.",
_ => "Unknown"
};
}
}
18 changes: 18 additions & 0 deletions src/Lib/JsonSourceGen/OpenAiJsonContext.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using MuzakBot.Lib.Models.OpenAi;

namespace MuzakBot.Lib;

/// <summary>
/// Source generation context for the OpenAI models.
/// </summary>
[JsonSourceGenerationOptions(
WriteIndented = false,
PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase,
GenerationMode = JsonSourceGenerationMode.Default,
DefaultIgnoreCondition = JsonIgnoreCondition.Never
)]
[JsonSerializable(typeof(OpenAiErrorResponse))]
[JsonSerializable(typeof(OpenAiErrorData))]
internal partial class OpenAiJsonContext : JsonSerializerContext
{
}
31 changes: 31 additions & 0 deletions src/Lib/Models/OpenAi/OpenAiErrorData.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
namespace MuzakBot.Lib;

/// <summary>
/// Holds data for an error that occurred while interacting with the OpenAI API.
/// </summary>
public sealed class OpenAiErrorData
{
/// <summary>
/// The error message.
/// </summary>
[JsonPropertyName("message")]
public string? Message { get; set; }

/// <summary>
/// The type of error that occurred.
/// </summary>
[JsonPropertyName("type")]
public string? ErrorType { get; set;}

/// <summary>
/// The parameter that caused the error.
/// </summary>
[JsonPropertyName("param")]
public string? Param { get; set; }

/// <summary>
/// The code of the error.
/// </summary>
[JsonPropertyName("code")]
public string? Code { get; set; }
}
13 changes: 13 additions & 0 deletions src/Lib/Models/OpenAi/OpenAiErrorResponse.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
namespace MuzakBot.Lib.Models.OpenAi;

/// <summary>
/// The response from the OpenAI API when an error occurs.
/// </summary>
public sealed class OpenAiErrorResponse
{
/// <summary>
/// The error message.
/// </summary>
[JsonPropertyName("error")]
public OpenAiErrorData Error { get; set; } = null!;
}