diff --git a/src/Libraries/Microsoft.Extensions.AI.Abstractions/Utilities/AIJsonSchemaCreateOptions.cs b/src/Libraries/Microsoft.Extensions.AI.Abstractions/Utilities/AIJsonSchemaCreateOptions.cs
index ea1f393f7e5..3a9c99c2e72 100644
--- a/src/Libraries/Microsoft.Extensions.AI.Abstractions/Utilities/AIJsonSchemaCreateOptions.cs
+++ b/src/Libraries/Microsoft.Extensions.AI.Abstractions/Utilities/AIJsonSchemaCreateOptions.cs
@@ -4,12 +4,14 @@
using System;
using System.Text.Json.Nodes;
+#pragma warning disable S1067 // Expressions should not be too complex
+
namespace Microsoft.Extensions.AI;
///
/// Provides options for configuring the behavior of JSON schema creation functionality.
///
-public sealed class AIJsonSchemaCreateOptions
+public sealed class AIJsonSchemaCreateOptions : IEquatable
{
///
/// Gets the default options instance.
@@ -40,4 +42,21 @@ public sealed class AIJsonSchemaCreateOptions
/// Gets a value indicating whether to mark all properties as required in the schema.
///
public bool RequireAllProperties { get; init; } = true;
+
+ ///
+ public bool Equals(AIJsonSchemaCreateOptions? other)
+ {
+ return other is not null &&
+ TransformSchemaNode == other.TransformSchemaNode &&
+ IncludeTypeInEnumSchemas == other.IncludeTypeInEnumSchemas &&
+ DisallowAdditionalProperties == other.DisallowAdditionalProperties &&
+ IncludeSchemaKeyword == other.IncludeSchemaKeyword &&
+ RequireAllProperties == other.RequireAllProperties;
+ }
+
+ ///
+ public override bool Equals(object? obj) => obj is AIJsonSchemaCreateOptions other && Equals(other);
+
+ ///
+ public override int GetHashCode() => (TransformSchemaNode, IncludeTypeInEnumSchemas, DisallowAdditionalProperties, IncludeSchemaKeyword, RequireAllProperties).GetHashCode();
}
diff --git a/src/Libraries/Microsoft.Extensions.AI/Functions/AIFunctionFactory.Utilities.cs b/src/Libraries/Microsoft.Extensions.AI/Functions/AIFunctionFactory.Utilities.cs
index 251059035db..cbafe78e5d3 100644
--- a/src/Libraries/Microsoft.Extensions.AI/Functions/AIFunctionFactory.Utilities.cs
+++ b/src/Libraries/Microsoft.Extensions.AI/Functions/AIFunctionFactory.Utilities.cs
@@ -1,6 +1,10 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+using System;
+using System.Buffers;
+using System.IO;
+using System.Reflection;
using System.Text.RegularExpressions;
using Microsoft.Shared.Diagnostics;
@@ -30,4 +34,104 @@ internal static string SanitizeMemberName(string memberName)
private static Regex InvalidNameCharsRegex() => _invalidNameCharsRegex;
private static readonly Regex _invalidNameCharsRegex = new("[^0-9A-Za-z_]", RegexOptions.Compiled);
#endif
+
+ /// Invokes the MethodInfo with the specified target object and arguments.
+ private static object? ReflectionInvoke(MethodInfo method, object? target, object?[]? arguments)
+ {
+#if NET
+ return method.Invoke(target, BindingFlags.DoNotWrapExceptions, binder: null, arguments, culture: null);
+#else
+ try
+ {
+ return method.Invoke(target, BindingFlags.Default, binder: null, arguments, culture: null);
+ }
+ catch (TargetInvocationException e) when (e.InnerException is not null)
+ {
+ // If we're targeting .NET Framework, such that BindingFlags.DoNotWrapExceptions
+ // is ignored, the original exception will be wrapped in a TargetInvocationException.
+ // Unwrap it and throw that original exception, maintaining its stack information.
+ System.Runtime.ExceptionServices.ExceptionDispatchInfo.Capture(e.InnerException).Throw();
+ throw;
+ }
+#endif
+ }
+
+ ///
+ /// Implements a simple write-only memory stream that uses pooled buffers.
+ ///
+ private sealed class PooledMemoryStream : Stream
+ {
+ private const int DefaultBufferSize = 4096;
+ private byte[] _buffer;
+ private int _position;
+
+ public PooledMemoryStream(int initialCapacity = DefaultBufferSize)
+ {
+ _buffer = ArrayPool.Shared.Rent(initialCapacity);
+ _position = 0;
+ }
+
+ public ReadOnlySpan GetBuffer() => _buffer.AsSpan(0, _position);
+ public override bool CanWrite => true;
+ public override bool CanRead => false;
+ public override bool CanSeek => false;
+ public override long Length => _position;
+ public override long Position
+ {
+ get => _position;
+ set => throw new NotSupportedException();
+ }
+
+ public override void Write(byte[] buffer, int offset, int count)
+ {
+ EnsureNotDisposed();
+ EnsureCapacity(_position + count);
+
+ Buffer.BlockCopy(buffer, offset, _buffer, _position, count);
+ _position += count;
+ }
+
+ public override void Flush()
+ {
+ }
+
+ public override int Read(byte[] buffer, int offset, int count) => throw new NotSupportedException();
+ public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException();
+ public override void SetLength(long value) => throw new NotSupportedException();
+
+ protected override void Dispose(bool disposing)
+ {
+ if (_buffer is not null)
+ {
+ ArrayPool.Shared.Return(_buffer);
+ _buffer = null!;
+ }
+
+ base.Dispose(disposing);
+ }
+
+ private void EnsureCapacity(int requiredCapacity)
+ {
+ if (requiredCapacity <= _buffer.Length)
+ {
+ return;
+ }
+
+ int newCapacity = Math.Max(requiredCapacity, _buffer.Length * 2);
+ byte[] newBuffer = ArrayPool.Shared.Rent(newCapacity);
+ Buffer.BlockCopy(_buffer, 0, newBuffer, 0, _position);
+
+ ArrayPool.Shared.Return(_buffer);
+ _buffer = newBuffer;
+ }
+
+ private void EnsureNotDisposed()
+ {
+ if (_buffer is null)
+ {
+ Throw();
+ static void Throw() => throw new ObjectDisposedException(nameof(PooledMemoryStream));
+ }
+ }
+ }
}
diff --git a/src/Libraries/Microsoft.Extensions.AI/Functions/AIFunctionFactory.cs b/src/Libraries/Microsoft.Extensions.AI/Functions/AIFunctionFactory.cs
index 50a5afd14e7..0aff0901c7a 100644
--- a/src/Libraries/Microsoft.Extensions.AI/Functions/AIFunctionFactory.cs
+++ b/src/Libraries/Microsoft.Extensions.AI/Functions/AIFunctionFactory.cs
@@ -2,12 +2,13 @@
// The .NET Foundation licenses this file to you under the MIT license.
using System;
+using System.Collections.Concurrent;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
-using System.IO;
using System.Linq;
using System.Reflection;
+using System.Runtime.CompilerServices;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Text.Json.Serialization.Metadata;
@@ -42,7 +43,7 @@ public static AIFunction Create(Delegate method, AIFunctionFactoryOptions? optio
{
_ = Throw.IfNull(method);
- return new ReflectionAIFunction(method.Method, method.Target, options ?? _defaultOptions);
+ return ReflectionAIFunction.Build(method.Method, method.Target, options ?? _defaultOptions);
}
/// Creates an instance for a method, specified via a delegate.
@@ -68,12 +69,12 @@ public static AIFunction Create(Delegate method, string? name = null, string? de
? _defaultOptions
: new()
{
- SerializerOptions = serializerOptions ?? _defaultOptions.SerializerOptions,
Name = name,
- Description = description
+ Description = description,
+ SerializerOptions = serializerOptions,
};
- return new ReflectionAIFunction(method.Method, method.Target, createOptions);
+ return ReflectionAIFunction.Build(method.Method, method.Target, createOptions);
}
///
@@ -100,7 +101,7 @@ public static AIFunction Create(Delegate method, string? name = null, string? de
public static AIFunction Create(MethodInfo method, object? target, AIFunctionFactoryOptions? options)
{
_ = Throw.IfNull(method);
- return new ReflectionAIFunction(method, target, options ?? _defaultOptions);
+ return ReflectionAIFunction.Build(method, target, options ?? _defaultOptions);
}
///
@@ -129,44 +130,23 @@ public static AIFunction Create(MethodInfo method, object? target, string? name
{
_ = Throw.IfNull(method);
- AIFunctionFactoryOptions? createOptions = serializerOptions is null && name is null && description is null
+ AIFunctionFactoryOptions createOptions = serializerOptions is null && name is null && description is null
? _defaultOptions
: new()
{
- SerializerOptions = serializerOptions ?? _defaultOptions.SerializerOptions,
Name = name,
- Description = description
+ Description = description,
+ SerializerOptions = serializerOptions,
};
- return new ReflectionAIFunction(method, target, createOptions);
+ return ReflectionAIFunction.Build(method, target, createOptions);
}
private sealed class ReflectionAIFunction : AIFunction
{
- private readonly MethodInfo _method;
- private readonly object? _target;
- private readonly Func, AIFunctionContext?, object?>[] _parameterMarshallers;
- private readonly Func