Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
9858a75
Created potential extensible FunctionInvokingChatClientV2
rogerbarreto Apr 19, 2025
3df56aa
Adding FunctionInvocationContextV2 and making AutoFunctionInvocation …
rogerbarreto Apr 19, 2025
48b50ee
Most Tests passing, missing identify reference issue
rogerbarreto Apr 19, 2025
e805c93
Using KernelArguments as Specialization of AIFunctionArguments and re…
rogerbarreto Apr 22, 2025
968190f
Fix typo
rogerbarreto Apr 22, 2025
193be17
Fix warnings
rogerbarreto Apr 22, 2025
a778718
Fix formatting
rogerbarreto Apr 22, 2025
6af06b8
Removing unused property
rogerbarreto Apr 23, 2025
2316427
Merge branch 'issues/11628-functioninvoking-extension' of https://git…
rogerbarreto Apr 23, 2025
bb97a21
Changes for 9.5
rogerbarreto Apr 29, 2025
7c014a5
SK update to use latest MEAI 9.5 Function invoking chat client
rogerbarreto Apr 29, 2025
340fbbe
Update to latest changes in 9.5.0-dev
rogerbarreto Apr 30, 2025
166bb29
Update latest MEAI version with Functin Invocking changes
rogerbarreto May 1, 2025
054fcf9
Remove unused files
rogerbarreto May 1, 2025
ebed086
Merge branch 'feature-msextensions-ai' into issues/11628-functioninvo…
rogerbarreto May 1, 2025
a7b531e
Revert nuget.config
rogerbarreto May 1, 2025
1bc29f8
Merge branch 'issues/11628-functioninvoking-extension' of https://git…
rogerbarreto May 1, 2025
b268871
Revert nuget.config
rogerbarreto May 1, 2025
c899c81
Remove unrelated test
rogerbarreto May 1, 2025
6b5eea3
Revert undesired changes
rogerbarreto May 1, 2025
f5847b9
Prevent unrelated warnings
rogerbarreto May 1, 2025
2503d58
Revert "Prevent unrelated warnings"
rogerbarreto May 1, 2025
2fbc65c
Merge branch 'feature-msextensions-ai' of https://github.com/microsof…
rogerbarreto May 1, 2025
d012c3d
Address latest changes to AutoFunctionInvocationContext
rogerbarreto May 2, 2025
c47cde7
Add missing UT
rogerbarreto May 2, 2025
c87d46d
Merge branch 'feature-msextensions-ai' into issues/11628-functioninvo…
rogerbarreto May 2, 2025
786df01
Fix warning
rogerbarreto May 2, 2025
cb40a56
Remove AIArguments + fix UT namespace
rogerbarreto May 2, 2025
062406f
Fix using order
rogerbarreto May 2, 2025
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 @@ -13,6 +13,8 @@
using System.Threading.Tasks;
using Microsoft.SemanticKernel.Http;

#pragma warning disable CA1859

namespace Microsoft.SemanticKernel.Plugins.OpenApi;

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
#pragma warning disable IDE0073 // The file header does not match the required text

// 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.Collections;
using System.Collections.Generic;

#pragma warning disable SA1111 // Closing parenthesis should be on line of last parameter
#pragma warning disable SA1112 // Closing parenthesis should be on line of opening parenthesis
#pragma warning disable SA1114 // Parameter list should follow declaration
#pragma warning disable CA1710 // Identifiers should have correct suffix
#pragma warning disable IDE0009 // Add this or Me qualification

namespace Microsoft.Extensions.AI;

/// <summary>Represents arguments to be used with <see cref="AIFunction.InvokeAsync"/>.</summary>
/// <remarks>
/// <see cref="AIFunctionArguments"/> is a dictionary of name/value pairs that are used
/// as inputs to an <see cref="AIFunction"/>. However, an instance carries additional non-nominal
/// information, such as an optional <see cref="IServiceProvider"/> that can be used by
/// an <see cref="AIFunction"/> if it needs to resolve any services from a dependency injection
/// container.
/// </remarks>
public class AIFunctionArgumentsV2 : IDictionary<string, object?>, IReadOnlyDictionary<string, object?>
{
/// <summary>The nominal arguments.</summary>
private readonly Dictionary<string, object?> _arguments;

/// <summary>Initializes a new instance of the <see cref="AIFunctionArguments"/> class.</summary>
/// <param name="comparer">The optional <see cref="IEqualityComparer{T}"/> to use for key comparisons.</param>
public AIFunctionArgumentsV2(IEqualityComparer<string>? comparer = null)
{
_arguments = new Dictionary<string, object?>(comparer);
}

/// <summary>
/// Initializes a new instance of the <see cref="AIFunctionArguments"/> class containing
/// the specified <paramref name="arguments"/>.
/// </summary>
/// <param name="arguments">The arguments represented by this instance.</param>
/// <param name="comparer">The <see cref="IEqualityComparer{T}"/> to be used.</param>
/// <remarks>
/// The <paramref name="arguments"/> reference will be stored if the instance is
/// already a <see cref="Dictionary{TKey, TValue}"/> and no <paramref name="comparer"/> is specified,
/// in which case all dictionary operations on this instance will be routed directly to that instance.
/// If <paramref name="arguments"/> is not a dictionary or a <paramref name="comparer"/> is specified,
/// a shallow clone of its data will be used to populate this instance.
/// A <see langword="null"/> <paramref name="arguments"/> is treated as an empty dictionary.
/// </remarks>
public AIFunctionArgumentsV2(IDictionary<string, object?>? arguments, IEqualityComparer<string>? comparer = null)
{
this._arguments = comparer is null
? arguments is null
? []
: arguments as Dictionary<string, object?> ??
new Dictionary<string, object?>(arguments)
: arguments is null
? new Dictionary<string, object?>(comparer)
: new Dictionary<string, object?>(arguments, comparer);
}

/// <summary>Gets or sets services optionally associated with these arguments.</summary>
public IServiceProvider? Services { get; set; }

/// <summary>Gets or sets additional context associated with these arguments.</summary>
/// <remarks>
/// The context is a dictionary of name/value pairs that can be used to store arbitrary
/// information for use by an <see cref="AIFunction"/> implementation. The meaning of this
/// data is left up to the implementer of the <see cref="AIFunction"/>.
/// </remarks>
public IDictionary<object, object?>? Context { get; set; }

/// <inheritdoc />
public object? this[string key]
{
get => _arguments[key];
set => _arguments[key] = value;
}

/// <inheritdoc />
public ICollection<string> Keys => _arguments.Keys;

/// <inheritdoc />
public ICollection<object?> Values => _arguments.Values;

/// <inheritdoc />
public int Count => _arguments.Count;

/// <inheritdoc />
public bool IsReadOnly => false;

/// <inheritdoc />
IEnumerable<string> IReadOnlyDictionary<string, object?>.Keys => Keys;

/// <inheritdoc />
IEnumerable<object?> IReadOnlyDictionary<string, object?>.Values => Values;

/// <inheritdoc />
public void Add(string key, object? value) => _arguments.Add(key, value);

/// <inheritdoc />
void ICollection<KeyValuePair<string, object?>>.Add(KeyValuePair<string, object?> item) =>
((ICollection<KeyValuePair<string, object?>>)_arguments).Add(item);

/// <inheritdoc />
public void Clear() => _arguments.Clear();

/// <inheritdoc />
public bool Contains(KeyValuePair<string, object?> item) =>
((ICollection<KeyValuePair<string, object?>>)_arguments).Contains(item);

/// <inheritdoc />
public bool ContainsKey(string key) => _arguments.ContainsKey(key);

/// <inheritdoc />
public void CopyTo(KeyValuePair<string, object?>[] array, int arrayIndex) =>
((ICollection<KeyValuePair<string, object?>>)_arguments).CopyTo(array, arrayIndex);

/// <inheritdoc />
public IEnumerator<KeyValuePair<string, object?>> GetEnumerator() => _arguments.GetEnumerator();

/// <inheritdoc />
public bool Remove(string key) => _arguments.Remove(key);

/// <inheritdoc />
bool ICollection<KeyValuePair<string, object?>>.Remove(KeyValuePair<string, object?> item) =>
((ICollection<KeyValuePair<string, object?>>)_arguments).Remove(item);

/// <inheritdoc />
public bool TryGetValue(string key, out object? value) => _arguments.TryGetValue(key, out value);

/// <inheritdoc />
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();

/// <summary>Gets the comparer used by the underlying dictionary.</summary>
protected IEqualityComparer<string> Comparer => _arguments.Comparer;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
#pragma warning disable IDE0073 // The file header does not match the required text

// 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.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;

#pragma warning disable IDE0055 // Fix formatting

namespace Microsoft.Extensions.AI;

/// <summary>Provides context for an in-flight function invocation.</summary>
public partial class FunctionInvocationContextV2
{
private static class Throw
{
/// <summary>
/// Throws an <see cref="System.ArgumentNullException"/> if the specified argument is <see langword="null"/>.
/// </summary>
/// <typeparam name="T">Argument type to be checked for <see langword="null"/>.</typeparam>
/// <param name="argument">Object to be checked for <see langword="null"/>.</param>
/// <param name="paramName">The name of the parameter being checked.</param>
/// <returns>The original value of <paramref name="argument"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[return: NotNull]
public static T IfNull<T>([NotNull] T argument, [CallerArgumentExpression(nameof(argument))] string paramName = "")
{
if (argument is null)
{
throw new ArgumentNullException(paramName);
}

return argument;
}

/// <summary>
/// Throws an <see cref="System.InvalidOperationException"/>.
/// </summary>
/// <param name="message">A message that describes the error.</param>
#if !NET6_0_OR_GREATER
[MethodImpl(MethodImplOptions.NoInlining)]
#endif
[DoesNotReturn]
public static void InvalidOperationException(string message)
=> throw new InvalidOperationException(message);

/// <summary>
/// Throws an <see cref="System.ArgumentOutOfRangeException"/>.
/// </summary>
/// <param name="paramName">The name of the parameter that caused the exception.</param>
#if !NET6_0_OR_GREATER
[MethodImpl(MethodImplOptions.NoInlining)]
#endif
[DoesNotReturn]
public static void ArgumentOutOfRangeException(string paramName)
=> throw new ArgumentOutOfRangeException(paramName);

/// <summary>
/// Throws an <see cref="System.ArgumentOutOfRangeException"/>.
/// </summary>
/// <param name="paramName">The name of the parameter that caused the exception.</param>
/// <param name="message">A message that describes the error.</param>
#if !NET6_0_OR_GREATER
[MethodImpl(MethodImplOptions.NoInlining)]
#endif
[DoesNotReturn]
public static void ArgumentOutOfRangeException(string paramName, string? message)
=> throw new ArgumentOutOfRangeException(paramName, message);

/// <summary>
/// Throws an <see cref="System.ArgumentOutOfRangeException"/>.
/// </summary>
/// <param name="paramName">The name of the parameter that caused the exception.</param>
/// <param name="actualValue">The value of the argument that caused this exception.</param>
/// <param name="message">A message that describes the error.</param>
#if !NET6_0_OR_GREATER
[MethodImpl(MethodImplOptions.NoInlining)]
#endif
[DoesNotReturn]
public static void ArgumentOutOfRangeException(string paramName, object? actualValue, string? message)
=> throw new ArgumentOutOfRangeException(paramName, actualValue, message);

/// <summary>
/// Throws an <see cref="System.ArgumentOutOfRangeException"/> if the specified number is less than min.
/// </summary>
/// <param name="argument">Number to be expected being less than min.</param>
/// <param name="min">The number that must be less than the argument.</param>
/// <param name="paramName">The name of the parameter being checked.</param>
/// <returns>The original value of <paramref name="argument"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int IfLessThan(int argument, int min, [CallerArgumentExpression(nameof(argument))] string paramName = "")
{
if (argument < min)
{
ArgumentOutOfRangeException(paramName, argument, $"Argument less than minimum value {min}");
}

return argument;
}
}

private static class LoggingHelpers
{
/// <summary>Serializes <paramref name="value" /> as JSON for logging purposes.</summary>
public static string AsJson<T>(T value, System.Text.Json.JsonSerializerOptions? options)
{
if (options?.TryGetTypeInfo(typeof(T), out var typeInfo) is true ||
AIJsonUtilities.DefaultOptions.TryGetTypeInfo(typeof(T), out typeInfo))
{
try
{
return System.Text.Json.JsonSerializer.Serialize(value, typeInfo);
}
#pragma warning disable CA1031 // Do not catch general exception types
catch
#pragma warning restore CA1031 // Do not catch general exception types
{
}
}

return "{}";
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
#pragma warning disable IDE0073 // The file header does not match the required text

// 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.Collections.Generic;

#pragma warning disable IDE1006 // Naming Styles
#pragma warning disable IDE0009 // Add this or Me qualification

namespace Microsoft.Extensions.AI;

/// <summary>Provides context for an in-flight function invocation.</summary>
public partial class FunctionInvocationContextV2
{
/// <summary>
/// A nop function used to allow <see cref="Function"/> to be non-nullable. Default instances of
/// <see cref="FunctionInvocationContext"/> start with this as the target function.
/// </summary>
private static readonly AIFunction _nopFunction = AIFunctionFactory.Create(() => { }, nameof(FunctionInvocationContext));
#pragma warning restore IDE1006 // Naming Styles

/// <summary>The chat contents associated with the operation that initiated this function call request.</summary>
private IList<ChatMessage> _messages = Array.Empty<ChatMessage>();

/// <summary>The AI function to be invoked.</summary>
private AIFunction _function = _nopFunction;

/// <summary>The function call content information associated with this invocation.</summary>
private FunctionCallContent? _callContent;

/// <summary>The arguments used with the function.</summary>
private AIFunctionArgumentsV2? _arguments;

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

/// <summary>Gets or sets the AI function to be invoked.</summary>
public AIFunction Function
{
get => _function;
set => _function = Throw.IfNull(value);
}

/// <summary>Gets or sets the arguments associated with this invocation.</summary>
public AIFunctionArgumentsV2 Arguments
{
get => _arguments ??= [];
set => _arguments = Throw.IfNull(value);
}

/// <summary>Gets or sets the function call content information associated with this invocation.</summary>
public FunctionCallContent CallContent
{
get => _callContent ??= new(string.Empty, _nopFunction.Name, EmptyReadOnlyDictionary<string, object?>.Instance);
set => _callContent = Throw.IfNull(value);
}

/// <summary>Gets or sets the chat contents associated with the operation that initiated this function call request.</summary>
public IList<ChatMessage> Messages
{
get => _messages;
set => _messages = Throw.IfNull(value);
}

/// <summary>Gets or sets the chat options associated with the operation that initiated this function call request.</summary>
public ChatOptions? Options { get; set; }

/// <summary>Gets or sets the number of this iteration with the underlying client.</summary>
/// <remarks>
/// The initial request to the client that passes along the chat contents provided to the <see cref="FunctionInvokingChatClient"/>
/// is iteration 1. If the client responds with a function call request, the next request to the client is iteration 2, and so on.
/// </remarks>
public int Iteration { get; set; }

/// <summary>Gets or sets the index of the function call within the iteration.</summary>
/// <remarks>
/// The response from the underlying client may include multiple function call requests.
/// This index indicates the position of the function call within the iteration.
/// </remarks>
public int FunctionCallIndex { get; set; }

/// <summary>Gets or sets the total number of function call requests within the iteration.</summary>
/// <remarks>
/// The response from the underlying client might include multiple function call requests.
/// This count indicates how many there were.
/// </remarks>
public int FunctionCount { get; set; }

/// <summary>Gets or sets a value indicating whether to terminate the request.</summary>
/// <remarks>
/// In response to a function call request, the function might be invoked, its result added to the chat contents,
/// and a new request issued to the wrapped client. If this property is set to <see langword="true"/>, that subsequent request
/// will not be issued and instead the loop immediately terminated rather than continuing until there are no
/// more function call requests in responses.
/// </remarks>
public bool Terminate { get; set; }

/// <summary>
/// Gets or sets a value indicating whether the context is happening in a streaming scenario.
/// </summary>
public bool IsStreaming { get; set; }
}
Loading
Loading