Skip to content
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
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ namespace Microsoft.Extensions.AI;
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 AIFunctionFactoryOptions _defaultOptions = new();

/// <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>
Expand All @@ -31,14 +31,14 @@ public static partial class AIFunctionFactory
/// <remarks>
/// <para>
/// Return values are serialized to <see cref="JsonElement"/> using <paramref name="options"/>'s
/// <see cref="AIFunctionFactoryCreateOptions.SerializerOptions"/>. Arguments that are not already of the expected type are
/// <see cref="AIFunctionFactoryOptions.SerializerOptions"/>. Arguments that are not already of the expected type are
/// marshaled to the expected type via JSON and using <paramref name="options"/>'s
/// <see cref="AIFunctionFactoryCreateOptions.SerializerOptions"/>. If the argument is a <see cref="JsonElement"/>,
/// <see cref="AIFunctionFactoryOptions.SerializerOptions"/>. If the argument is a <see cref="JsonElement"/>,
/// <see cref="JsonDocument"/>, or <see cref="JsonNode"/>, 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.
/// </para>
/// </remarks>
public static AIFunction Create(Delegate method, AIFunctionFactoryCreateOptions? options)
public static AIFunction Create(Delegate method, AIFunctionFactoryOptions? options)
{
_ = Throw.IfNull(method);

Expand All @@ -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()
{
Expand All @@ -90,14 +90,14 @@ public static AIFunction Create(Delegate method, string? name = null, string? de
/// <remarks>
/// <para>
/// Return values are serialized to <see cref="JsonElement"/> using <paramref name="options"/>'s
/// <see cref="AIFunctionFactoryCreateOptions.SerializerOptions"/>. Arguments that are not already of the expected type are
/// <see cref="AIFunctionFactoryOptions.SerializerOptions"/>. Arguments that are not already of the expected type are
/// marshaled to the expected type via JSON and using <paramref name="options"/>'s
/// <see cref="AIFunctionFactoryCreateOptions.SerializerOptions"/>. If the argument is a <see cref="JsonElement"/>,
/// <see cref="AIFunctionFactoryOptions.SerializerOptions"/>. If the argument is a <see cref="JsonElement"/>,
/// <see cref="JsonDocument"/>, or <see cref="JsonNode"/>, 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.
/// </para>
/// </remarks>
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);
Expand Down Expand Up @@ -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()
{
Expand Down Expand Up @@ -160,12 +160,13 @@ private sealed class ReflectionAIFunction : AIFunction
/// This should be <see langword="null"/> if and only if <paramref name="method"/> is a static method.
/// </param>
/// <param name="options">Function creation options.</param>
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)
{
Expand Down Expand Up @@ -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<DescriptionAttribute>(inherit: true)?.Description ?? string.Empty;
UnderlyingMethod = method;
AdditionalProperties = options.AdditionalProperties ?? EmptyReadOnlyDictionary<string, object?>.Instance;
JsonSerializerOptions = options.SerializerOptions;
JsonSerializerOptions = serializerOptions;
JsonSchema = AIJsonUtilities.CreateFunctionJsonSchema(
method,
title: Name,
Expand Down Expand Up @@ -308,7 +309,7 @@ static bool IsAsyncMethod(MethodInfo method)
/// Gets a delegate for handling the marshaling of a parameter.
/// </summary>
private static Func<IReadOnlyDictionary<string, object?>, AIFunctionContext?, object?> GetParameterMarshaller(
AIFunctionFactoryCreateOptions options,
JsonSerializerOptions serializerOptions,
ParameterInfo parameter,
ref bool sawAIFunctionContext)
{
Expand Down Expand Up @@ -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<string, object?> arguments, AIFunctionContext? _) =>
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,40 +6,34 @@
using System.ComponentModel;
using System.Reflection;
using System.Text.Json;
using Microsoft.Shared.Diagnostics;

namespace Microsoft.Extensions.AI;

/// <summary>
/// Represents options that can be provided when creating an <see cref="AIFunction"/> from a method.
/// </summary>
public sealed class AIFunctionFactoryCreateOptions
public sealed class AIFunctionFactoryOptions
{
private JsonSerializerOptions _options = AIJsonUtilities.DefaultOptions;
private AIJsonSchemaCreateOptions _jsonSchemaCreateOptions = AIJsonSchemaCreateOptions.Default;

/// <summary>
/// Initializes a new instance of the <see cref="AIFunctionFactoryCreateOptions"/> class.
/// Initializes a new instance of the <see cref="AIFunctionFactoryOptions"/> class.
/// </summary>
public AIFunctionFactoryCreateOptions()
public AIFunctionFactoryOptions()
{
}

/// <summary>Gets or sets the <see cref="JsonSerializerOptions"/> used to marshal .NET values being passed to the underlying delegate.</summary>
public JsonSerializerOptions SerializerOptions
{
get => _options;
set => _options = Throw.IfNull(value);
}
/// <remarks>
/// If no value has been specified, the <see cref="AIJsonUtilities.DefaultOptions"/> instance will be used.
/// </remarks>
public JsonSerializerOptions? SerializerOptions { get; set; }

/// <summary>
/// Gets or sets the <see cref="AIJsonSchemaCreateOptions"/> governing the generation of JSON schemas for the function.
/// </summary>
public AIJsonSchemaCreateOptions JsonSchemaCreateOptions
{
get => _jsonSchemaCreateOptions;
set => _jsonSchemaCreateOptions = Throw.IfNull(value);
}
/// <remarks>
/// If no value has been specified, the <see cref="AIJsonSchemaCreateOptions.Default"/> instance will be used.
/// </remarks>
public AIJsonSchemaCreateOptions? JsonSchemaCreateOptions { get; set; }

/// <summary>Gets or sets the name to use for the function.</summary>
/// <value>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ public void AIFunctionFactoryCreateOptions_ValuesPropagateToAIFunction()
{
IReadOnlyDictionary<string, object?> metadata = new Dictionary<string, object?> { ["a"] = "b" };

var options = new AIFunctionFactoryCreateOptions
var options = new AIFunctionFactoryOptions
{
Name = "test name",
Description = "test description",
Expand All @@ -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);
}
}