Skip to content

Commit 66eca03

Browse files
Reinstate caching in schema generation (#5908)
* Reinstate caching in schema generation * Address feedback. * Only cache on the AIFunctionFactory level. * Reorder properties, * Rename serialization helper.
1 parent 152f142 commit 66eca03

File tree

4 files changed

+345
-160
lines changed

4 files changed

+345
-160
lines changed

src/Libraries/Microsoft.Extensions.AI.Abstractions/Utilities/AIJsonSchemaCreateOptions.cs

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,14 @@
44
using System;
55
using System.Text.Json.Nodes;
66

7+
#pragma warning disable S1067 // Expressions should not be too complex
8+
79
namespace Microsoft.Extensions.AI;
810

911
/// <summary>
1012
/// Provides options for configuring the behavior of <see cref="AIJsonUtilities"/> JSON schema creation functionality.
1113
/// </summary>
12-
public sealed class AIJsonSchemaCreateOptions
14+
public sealed class AIJsonSchemaCreateOptions : IEquatable<AIJsonSchemaCreateOptions>
1315
{
1416
/// <summary>
1517
/// Gets the default options instance.
@@ -40,4 +42,21 @@ public sealed class AIJsonSchemaCreateOptions
4042
/// Gets a value indicating whether to mark all properties as required in the schema.
4143
/// </summary>
4244
public bool RequireAllProperties { get; init; } = true;
45+
46+
/// <inheritdoc/>
47+
public bool Equals(AIJsonSchemaCreateOptions? other)
48+
{
49+
return other is not null &&
50+
TransformSchemaNode == other.TransformSchemaNode &&
51+
IncludeTypeInEnumSchemas == other.IncludeTypeInEnumSchemas &&
52+
DisallowAdditionalProperties == other.DisallowAdditionalProperties &&
53+
IncludeSchemaKeyword == other.IncludeSchemaKeyword &&
54+
RequireAllProperties == other.RequireAllProperties;
55+
}
56+
57+
/// <inheritdoc />
58+
public override bool Equals(object? obj) => obj is AIJsonSchemaCreateOptions other && Equals(other);
59+
60+
/// <inheritdoc />
61+
public override int GetHashCode() => (TransformSchemaNode, IncludeTypeInEnumSchemas, DisallowAdditionalProperties, IncludeSchemaKeyword, RequireAllProperties).GetHashCode();
4362
}

src/Libraries/Microsoft.Extensions.AI/Functions/AIFunctionFactory.Utilities.cs

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using System;
5+
using System.Buffers;
6+
using System.IO;
7+
using System.Reflection;
48
using System.Text.RegularExpressions;
59
using Microsoft.Shared.Diagnostics;
610

@@ -30,4 +34,104 @@ internal static string SanitizeMemberName(string memberName)
3034
private static Regex InvalidNameCharsRegex() => _invalidNameCharsRegex;
3135
private static readonly Regex _invalidNameCharsRegex = new("[^0-9A-Za-z_]", RegexOptions.Compiled);
3236
#endif
37+
38+
/// <summary>Invokes the MethodInfo with the specified target object and arguments.</summary>
39+
private static object? ReflectionInvoke(MethodInfo method, object? target, object?[]? arguments)
40+
{
41+
#if NET
42+
return method.Invoke(target, BindingFlags.DoNotWrapExceptions, binder: null, arguments, culture: null);
43+
#else
44+
try
45+
{
46+
return method.Invoke(target, BindingFlags.Default, binder: null, arguments, culture: null);
47+
}
48+
catch (TargetInvocationException e) when (e.InnerException is not null)
49+
{
50+
// If we're targeting .NET Framework, such that BindingFlags.DoNotWrapExceptions
51+
// is ignored, the original exception will be wrapped in a TargetInvocationException.
52+
// Unwrap it and throw that original exception, maintaining its stack information.
53+
System.Runtime.ExceptionServices.ExceptionDispatchInfo.Capture(e.InnerException).Throw();
54+
throw;
55+
}
56+
#endif
57+
}
58+
59+
/// <summary>
60+
/// Implements a simple write-only memory stream that uses pooled buffers.
61+
/// </summary>
62+
private sealed class PooledMemoryStream : Stream
63+
{
64+
private const int DefaultBufferSize = 4096;
65+
private byte[] _buffer;
66+
private int _position;
67+
68+
public PooledMemoryStream(int initialCapacity = DefaultBufferSize)
69+
{
70+
_buffer = ArrayPool<byte>.Shared.Rent(initialCapacity);
71+
_position = 0;
72+
}
73+
74+
public ReadOnlySpan<byte> GetBuffer() => _buffer.AsSpan(0, _position);
75+
public override bool CanWrite => true;
76+
public override bool CanRead => false;
77+
public override bool CanSeek => false;
78+
public override long Length => _position;
79+
public override long Position
80+
{
81+
get => _position;
82+
set => throw new NotSupportedException();
83+
}
84+
85+
public override void Write(byte[] buffer, int offset, int count)
86+
{
87+
EnsureNotDisposed();
88+
EnsureCapacity(_position + count);
89+
90+
Buffer.BlockCopy(buffer, offset, _buffer, _position, count);
91+
_position += count;
92+
}
93+
94+
public override void Flush()
95+
{
96+
}
97+
98+
public override int Read(byte[] buffer, int offset, int count) => throw new NotSupportedException();
99+
public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException();
100+
public override void SetLength(long value) => throw new NotSupportedException();
101+
102+
protected override void Dispose(bool disposing)
103+
{
104+
if (_buffer is not null)
105+
{
106+
ArrayPool<byte>.Shared.Return(_buffer);
107+
_buffer = null!;
108+
}
109+
110+
base.Dispose(disposing);
111+
}
112+
113+
private void EnsureCapacity(int requiredCapacity)
114+
{
115+
if (requiredCapacity <= _buffer.Length)
116+
{
117+
return;
118+
}
119+
120+
int newCapacity = Math.Max(requiredCapacity, _buffer.Length * 2);
121+
byte[] newBuffer = ArrayPool<byte>.Shared.Rent(newCapacity);
122+
Buffer.BlockCopy(_buffer, 0, newBuffer, 0, _position);
123+
124+
ArrayPool<byte>.Shared.Return(_buffer);
125+
_buffer = newBuffer;
126+
}
127+
128+
private void EnsureNotDisposed()
129+
{
130+
if (_buffer is null)
131+
{
132+
Throw();
133+
static void Throw() => throw new ObjectDisposedException(nameof(PooledMemoryStream));
134+
}
135+
}
136+
}
33137
}

0 commit comments

Comments
 (0)