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

M.E.AI - Add support for string JsonElement parsing to primitive #1

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
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
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ public static partial class AIJsonUtilities
/// <summary>Gets the <see cref="JsonSerializerOptions"/> singleton used as the default in JSON serialization operations.</summary>
public static JsonSerializerOptions DefaultOptions { get; } = CreateDefaultOptions();

/// <summary>Gets the default <see cref="IFormatProvider"/> to use when parsing JSON strings into primitive types.</summary>
public static IFormatProvider DefaultFormatProvider { get; } = System.Globalization.CultureInfo.InvariantCulture;

/// <summary>Creates the default <see cref="JsonSerializerOptions"/> to use for serialization-related operations.</summary>
[UnconditionalSuppressMessage("AotAnalysis", "IL3050", Justification = "DefaultJsonTypeInfoResolver is only used when reflection-based serialization is enabled")]
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026", Justification = "DefaultJsonTypeInfoResolver is only used when reflection-based serialization is enabled")]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,22 @@ public static partial class AIFunctionFactory
/// <summary>Holds the default options instance used when creating function.</summary>
private static readonly AIFunctionFactoryCreateOptions _defaultOptions = new();

private static readonly Dictionary<Type, Func<string, IFormatProvider?, object>> _jsonStringParsers = new(12)
{
{ typeof(bool), (s, _) => bool.Parse(s) },
{ typeof(int), (s, format) => int.Parse(s, format) },
{ typeof(uint), (s, format) => uint.Parse(s, format) },
{ typeof(long), (s, format) => long.Parse(s, format) },
{ typeof(ulong), (s, format) => ulong.Parse(s, format) },
{ typeof(float), (s, format) => float.Parse(s, format) },
{ typeof(double), (s, format) => double.Parse(s, format) },
{ typeof(decimal), (s, format) => decimal.Parse(s, format) },
{ typeof(short), (s, format) => short.Parse(s, format) },
{ typeof(ushort), (s, format) => ushort.Parse(s, format ) },
{ typeof(byte), (s, format) => byte.Parse(s, format) },
{ typeof(sbyte), (s, format) => sbyte.Parse(s, format) }
};

/// <summary>Creates an <see cref="AIFunction"/> instance for a method, specified via a delegate.</summary>
/// <param name="method">The method to be represented via the created <see cref="AIFunction"/>.</param>
/// <param name="options">Metadata to use to override defaults inferred from <paramref name="method"/>.</param>
Expand Down Expand Up @@ -378,7 +394,7 @@ static bool IsAsyncMethod(MethodInfo method)
{
null => null, // Return as-is if null -- if the parameter is a struct this will be handled by MethodInfo.Invoke
_ when parameterType.IsInstanceOfType(value) => value, // Do nothing if value is assignable to parameter type
JsonElement element => JsonSerializer.Deserialize(element, typeInfo),
JsonElement element => DeserializeJsonElement(element),
JsonDocument doc => JsonSerializer.Deserialize(doc, typeInfo),
JsonNode node => JsonSerializer.Deserialize(node, typeInfo),
_ => MarshallViaJsonRoundtrip(value),
Expand All @@ -399,6 +415,18 @@ static bool IsAsyncMethod(MethodInfo method)
}
#pragma warning restore CA1031 // Do not catch general exception types
}

object? DeserializeJsonElement(JsonElement element)
RogerBarreto marked this conversation as resolved.
Show resolved Hide resolved
{
if (parameterType != typeof(string)
&& element.ValueKind == JsonValueKind.String
&& _jsonStringParsers.TryGetValue(parameterType, out var jsonStringParser))
{
return jsonStringParser(element.GetString()!, options.FormatProvider);
}

return JsonSerializer.Deserialize(element, typeInfo);
}
}

// There was no argument for the parameter. Try to use a default value.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ namespace Microsoft.Extensions.AI;
public sealed class AIFunctionFactoryCreateOptions
{
private JsonSerializerOptions _options = AIJsonUtilities.DefaultOptions;
private IFormatProvider _formatProvider = AIJsonUtilities.DefaultFormatProvider;
private AIJsonSchemaCreateOptions _schemaCreateOptions = AIJsonSchemaCreateOptions.Default;

/// <summary>
Expand All @@ -32,6 +33,13 @@ public JsonSerializerOptions SerializerOptions
set => _options = Throw.IfNull(value);
}

/// <summary>Gets or sets the <see cref="IFormatProvider"/> used to parse JSON strings into primitive types.</summary>
public IFormatProvider FormatProvider
{
get => _formatProvider;
set => _formatProvider = Throw.IfNull(value);
}

/// <summary>
/// Gets or sets the <see cref="AIJsonSchemaCreateOptions"/> governing the generation of JSON schemas for the function.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,18 +121,23 @@ public async Task AIFunctionFactory_JsonElementValues_ValuesDeserialized()
Dictionary<string, object?> arguments = JsonSerializer.Deserialize<Dictionary<string, object?>>("""
{
"a": ["Monday", "Tuesday", "Wednesday"],
"b": 123.4,
"b1": 123.4,
"b2": "123.4",
"c": "072c2d93-7cf6-4d0d-aebc-acc51e6ee7ee",
"d": {
"property1": "42",
"property2": "43",
"property3": "44"
}
},
"e1": true,
"e2": "false"
}
""", TestJsonSerializerContext.Default.Options)!;
Assert.All(arguments.Values, v => Assert.IsType<JsonElement>(v));

AIFunction function = AIFunctionFactory.Create((DayOfWeek[] a, double b, Guid c, Dictionary<string, string> d) => b, serializerOptions: TestJsonSerializerContext.Default.Options);
AIFunction function = AIFunctionFactory.Create(
(DayOfWeek[] a, double b1, float b2, Guid c, Dictionary<string, string> d, bool e1, bool e2) => b1,
serializerOptions: TestJsonSerializerContext.Default.Options);
var result = await function.InvokeAsync(arguments);
AssertExtensions.EqualFunctionCallResults(123.4, result);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,6 @@ namespace Microsoft.Extensions.AI;
[JsonSerializable(typeof(DayOfWeek[]))] // Used in Content tests
[JsonSerializable(typeof(Guid))] // Used in Content tests
[JsonSerializable(typeof(decimal))] // Used in Content tests
[JsonSerializable(typeof(bool))] // Used in Content tests
[JsonSerializable(typeof(float))] // Used in Content tests
internal sealed partial class TestJsonSerializerContext : JsonSerializerContext;