diff --git a/src/Libraries/Microsoft.Extensions.AI/Functions/AIFunctionFactory.cs b/src/Libraries/Microsoft.Extensions.AI/Functions/AIFunctionFactory.cs index 8f473fe3f7a..d0d3385749e 100644 --- a/src/Libraries/Microsoft.Extensions.AI/Functions/AIFunctionFactory.cs +++ b/src/Libraries/Microsoft.Extensions.AI/Functions/AIFunctionFactory.cs @@ -22,7 +22,7 @@ namespace Microsoft.Extensions.AI; public static partial class AIFunctionFactory { /// Holds the default options instance used when creating function. - private static readonly AIFunctionFactoryCreateOptions _defaultOptions = new(); + private static readonly AIFunctionFactoryOptions _defaultOptions = new(); /// Creates an instance for a method, specified via a delegate. /// The method to be represented via the created . @@ -31,14 +31,14 @@ public static partial class AIFunctionFactory /// /// /// Return values are serialized to using 's - /// . Arguments that are not already of the expected type are + /// . Arguments that are not already of the expected type are /// marshaled to the expected type via JSON and using 's - /// . If the argument is a , + /// . If the argument is a , /// , or , it is deserialized directly. If the argument is anything else unknown, /// it is round-tripped through JSON, serializing the object as JSON and then deserializing it to the expected type. /// /// - public static AIFunction Create(Delegate method, AIFunctionFactoryCreateOptions? options) + public static AIFunction Create(Delegate method, AIFunctionFactoryOptions? options) { _ = Throw.IfNull(method); @@ -64,7 +64,7 @@ public static AIFunction Create(Delegate method, string? name = null, string? de { _ = Throw.IfNull(method); - AIFunctionFactoryCreateOptions createOptions = serializerOptions is null && name is null && description is null + AIFunctionFactoryOptions createOptions = serializerOptions is null && name is null && description is null ? _defaultOptions : new() { @@ -90,14 +90,14 @@ public static AIFunction Create(Delegate method, string? name = null, string? de /// /// /// Return values are serialized to using 's - /// . Arguments that are not already of the expected type are + /// . Arguments that are not already of the expected type are /// marshaled to the expected type via JSON and using 's - /// . If the argument is a , + /// . If the argument is a , /// , or , it is deserialized directly. If the argument is anything else unknown, /// it is round-tripped through JSON, serializing the object as JSON and then deserializing it to the expected type. /// /// - public static AIFunction Create(MethodInfo method, object? target, AIFunctionFactoryCreateOptions? options) + public static AIFunction Create(MethodInfo method, object? target, AIFunctionFactoryOptions? options) { _ = Throw.IfNull(method); return new ReflectionAIFunction(method, target, options ?? _defaultOptions); @@ -129,7 +129,7 @@ public static AIFunction Create(MethodInfo method, object? target, string? name { _ = Throw.IfNull(method); - AIFunctionFactoryCreateOptions? createOptions = serializerOptions is null && name is null && description is null + AIFunctionFactoryOptions? createOptions = serializerOptions is null && name is null && description is null ? _defaultOptions : new() { @@ -160,12 +160,13 @@ private sealed class ReflectionAIFunction : AIFunction /// This should be if and only if is a static method. /// /// Function creation options. - public ReflectionAIFunction(MethodInfo method, object? target, AIFunctionFactoryCreateOptions options) + public ReflectionAIFunction(MethodInfo method, object? target, AIFunctionFactoryOptions options) { _ = Throw.IfNull(method); _ = Throw.IfNull(options); - options.SerializerOptions.MakeReadOnly(); + JsonSerializerOptions serializerOptions = options.SerializerOptions ?? AIJsonUtilities.DefaultOptions; + serializerOptions.MakeReadOnly(); if (method.ContainsGenericParameters) { @@ -222,20 +223,20 @@ static bool IsAsyncMethod(MethodInfo method) bool sawAIContextParameter = false; for (int i = 0; i < parameters.Length; i++) { - _parameterMarshallers[i] = GetParameterMarshaller(options, parameters[i], ref sawAIContextParameter); + _parameterMarshallers[i] = GetParameterMarshaller(serializerOptions, parameters[i], ref sawAIContextParameter); } _needsAIFunctionContext = sawAIContextParameter; // Get the return type and a marshaling func for the return value. _returnMarshaller = GetReturnMarshaller(method, out Type returnType); - _returnTypeInfo = returnType != typeof(void) ? options.SerializerOptions.GetTypeInfo(returnType) : null; + _returnTypeInfo = returnType != typeof(void) ? serializerOptions.GetTypeInfo(returnType) : null; Name = functionName; Description = options.Description ?? method.GetCustomAttribute(inherit: true)?.Description ?? string.Empty; UnderlyingMethod = method; AdditionalProperties = options.AdditionalProperties ?? EmptyReadOnlyDictionary.Instance; - JsonSerializerOptions = options.SerializerOptions; + JsonSerializerOptions = serializerOptions; JsonSchema = AIJsonUtilities.CreateFunctionJsonSchema( method, title: Name, @@ -308,7 +309,7 @@ static bool IsAsyncMethod(MethodInfo method) /// Gets a delegate for handling the marshaling of a parameter. /// private static Func, AIFunctionContext?, object?> GetParameterMarshaller( - AIFunctionFactoryCreateOptions options, + JsonSerializerOptions serializerOptions, ParameterInfo parameter, ref bool sawAIFunctionContext) { @@ -336,7 +337,7 @@ static bool IsAsyncMethod(MethodInfo method) // Resolve the contract used to marshal the value from JSON -- can throw if not supported or not found. Type parameterType = parameter.ParameterType; - JsonTypeInfo typeInfo = options.SerializerOptions.GetTypeInfo(parameterType); + JsonTypeInfo typeInfo = serializerOptions.GetTypeInfo(parameterType); // Create a marshaller that simply looks up the parameter by name in the arguments dictionary. return (IReadOnlyDictionary arguments, AIFunctionContext? _) => @@ -359,7 +360,7 @@ static bool IsAsyncMethod(MethodInfo method) #pragma warning disable CA1031 // Do not catch general exception types try { - string json = JsonSerializer.Serialize(value, options.SerializerOptions.GetTypeInfo(value.GetType())); + string json = JsonSerializer.Serialize(value, serializerOptions.GetTypeInfo(value.GetType())); return JsonSerializer.Deserialize(json, typeInfo); } catch diff --git a/src/Libraries/Microsoft.Extensions.AI/Functions/AIFunctionFactoryCreateOptions.cs b/src/Libraries/Microsoft.Extensions.AI/Functions/AIFunctionFactoryOptions.cs similarity index 75% rename from src/Libraries/Microsoft.Extensions.AI/Functions/AIFunctionFactoryCreateOptions.cs rename to src/Libraries/Microsoft.Extensions.AI/Functions/AIFunctionFactoryOptions.cs index 27db24acadb..ac285241469 100644 --- a/src/Libraries/Microsoft.Extensions.AI/Functions/AIFunctionFactoryCreateOptions.cs +++ b/src/Libraries/Microsoft.Extensions.AI/Functions/AIFunctionFactoryOptions.cs @@ -6,40 +6,34 @@ using System.ComponentModel; using System.Reflection; using System.Text.Json; -using Microsoft.Shared.Diagnostics; namespace Microsoft.Extensions.AI; /// /// Represents options that can be provided when creating an from a method. /// -public sealed class AIFunctionFactoryCreateOptions +public sealed class AIFunctionFactoryOptions { - private JsonSerializerOptions _options = AIJsonUtilities.DefaultOptions; - private AIJsonSchemaCreateOptions _jsonSchemaCreateOptions = AIJsonSchemaCreateOptions.Default; - /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - public AIFunctionFactoryCreateOptions() + public AIFunctionFactoryOptions() { } /// Gets or sets the used to marshal .NET values being passed to the underlying delegate. - public JsonSerializerOptions SerializerOptions - { - get => _options; - set => _options = Throw.IfNull(value); - } + /// + /// If no value has been specified, the instance will be used. + /// + public JsonSerializerOptions? SerializerOptions { get; set; } /// /// Gets or sets the governing the generation of JSON schemas for the function. /// - public AIJsonSchemaCreateOptions JsonSchemaCreateOptions - { - get => _jsonSchemaCreateOptions; - set => _jsonSchemaCreateOptions = Throw.IfNull(value); - } + /// + /// If no value has been specified, the instance will be used. + /// + public AIJsonSchemaCreateOptions? JsonSchemaCreateOptions { get; set; } /// Gets or sets the name to use for the function. /// diff --git a/test/Libraries/Microsoft.Extensions.AI.Tests/Functions/AIFunctionFactoryTest.cs b/test/Libraries/Microsoft.Extensions.AI.Tests/Functions/AIFunctionFactoryTest.cs index d8673e36c4d..daece288cca 100644 --- a/test/Libraries/Microsoft.Extensions.AI.Tests/Functions/AIFunctionFactoryTest.cs +++ b/test/Libraries/Microsoft.Extensions.AI.Tests/Functions/AIFunctionFactoryTest.cs @@ -160,7 +160,7 @@ public void AIFunctionFactoryCreateOptions_ValuesPropagateToAIFunction() { IReadOnlyDictionary metadata = new Dictionary { ["a"] = "b" }; - var options = new AIFunctionFactoryCreateOptions + var options = new AIFunctionFactoryOptions { Name = "test name", Description = "test description", @@ -181,14 +181,14 @@ public void AIFunctionFactoryCreateOptions_ValuesPropagateToAIFunction() } [Fact] - public void AIFunctionFactoryCreateOptions_SchemaOptions_HasExpectedDefaults() + public void AIFunctionFactoryOptions_DefaultValues() { - var options = new AIFunctionFactoryCreateOptions(); - var schemaOptions = options.JsonSchemaCreateOptions; + AIFunctionFactoryOptions options = new(); - Assert.NotNull(schemaOptions); - Assert.True(schemaOptions.IncludeTypeInEnumSchemas); - Assert.True(schemaOptions.RequireAllProperties); - Assert.True(schemaOptions.DisallowAdditionalProperties); + Assert.Null(options.Name); + Assert.Null(options.Description); + Assert.Null(options.AdditionalProperties); + Assert.Null(options.SerializerOptions); + Assert.Null(options.JsonSchemaCreateOptions); } }