using System.Diagnostics.CodeAnalysis;
using System.Text.Json;
namespace AIStudio.Assistants.SWOTAnalysis;
///
/// Reads the JSON object out of the answer of a model which works on the SWOT analysis.
///
internal static class SwotModelAnswer
{
private static readonly JsonSerializerOptions JSON_OPTIONS = new()
{
PropertyNameCaseInsensitive = true,
AllowTrailingCommas = true,
ReadCommentHandling = JsonCommentHandling.Skip,
RespectNullableAnnotations = true,
};
///
/// Reads the JSON object of a model answer into the given type, without checking its content.
///
///
/// A null where the type expects a value fails the reading, and the issue names the field. A null
/// inside a list, though, is read as such, since the annotations of list items do not count.
///
/// The answer of the model.
/// The object, or null when reading failed.
/// Why reading failed, written for the model; empty on success.
/// Whether the answer holds an object which could be read.
public static bool TryDeserialize(string answer, [NotNullWhen(true)] out T? value, out string issue) where T : class
{
value = null;
issue = string.Empty;
var json = ModelAnswerJson.ExtractObject(answer);
if (json.IsWhiteSpace())
{
issue = "The answer contains no complete JSON object.";
return false;
}
try
{
value = JsonSerializer.Deserialize(json, JSON_OPTIONS);
}
catch (JsonException e)
{
issue = $"The JSON object cannot be read: {e.Message}";
return false;
}
if (value is null)
{
issue = "The JSON object is empty.";
return false;
}
return true;
}
///
/// Reads an answer, and asks the model once more when the answer cannot be read.
///
///
/// The caller sends the repair request into the same thread, so the model sees its own answer next
/// to what is wrong with it. Exactly one round, as in the Slide Assistant: a model which misses the
/// form twice in a row seldom hits it the third time, and every round costs as much as the first.
///
/// The first answer of the model.
/// Reads an answer and names what is wrong with it.
/// Gets the issue of the first answer, sends RepairRequestOf it, and returns the new answer; an empty one when the provider failed.
/// The result of the first answer which could be read, or the issue of the last one.
public static async Task> ReadWithOneRepairAsync(string answer, SwotAnswerReader read, Func> askAgain) where TResult : class
{
if (read(answer, out var result, out var issue))
return new(result, string.Empty);
var repairedAnswer = await askAgain(issue);
if (string.IsNullOrWhiteSpace(repairedAnswer))
return new(null, issue);
return read(repairedAnswer, out result, out issue) ? new(result, string.Empty) : new(null, issue);
}
///
/// The request which asks a model for its answer once more, in the expected form.
///
/// What is wrong with the answer, as the parser named it.
public static string RepairRequestOf(string issue) =>
$"""
Your answer cannot be used yet: {issue}
Answer with the complete corrected JSON object and nothing else.
""";
///
/// Whether an item of a list read from a model answer is null.
///
///
/// For removing the nulls a model wrote into a list: the annotations of list items do not count
/// when reading, so a list may hold a null although its type says otherwise.
///
/// The item of the list.
/// Whether the item is null.
public static bool IsNull(object? item) => item is null;
}