// Copyright (c) Microsoft. All rights reserved.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.AI;
namespace Microsoft.Agents.AI;
///
/// Internal agent decorator that adds function invocation middleware logic.
///
internal sealed class FunctionInvocationDelegatingAgent : DelegatingAIAgent
{
private readonly Func>, CancellationToken, ValueTask> _delegateFunc;
internal FunctionInvocationDelegatingAgent(AIAgent innerAgent, Func>, CancellationToken, ValueTask> delegateFunc) : base(innerAgent)
{
this._delegateFunc = delegateFunc;
}
protected override Task RunCoreAsync(IEnumerable messages, AgentSession? session = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default)
=> this.InnerAgent.RunAsync(messages, session, this.AgentRunOptionsWithFunctionMiddleware(options), cancellationToken);
protected override IAsyncEnumerable RunCoreStreamingAsync(IEnumerable messages, AgentSession? session = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default)
=> this.InnerAgent.RunStreamingAsync(messages, session, this.AgentRunOptionsWithFunctionMiddleware(options), cancellationToken);
// Decorate options to add the middleware function
private AgentRunOptions? AgentRunOptionsWithFunctionMiddleware(AgentRunOptions? options)
{
if (options is null || options.GetType() == typeof(AgentRunOptions))
{
options = new ChatClientAgentRunOptions()
{
ResponseFormat = options?.ResponseFormat,
AllowBackgroundResponses = options?.AllowBackgroundResponses,
ContinuationToken = options?.ContinuationToken,
AdditionalProperties = options?.AdditionalProperties,
};
}
if (options is not ChatClientAgentRunOptions aco)
{
throw new NotSupportedException($"Function Invocation Middleware is only supported without options or with {nameof(ChatClientAgentRunOptions)}.");
}
var originalFactory = aco.ChatClientFactory;
aco.ChatClientFactory = chatClient =>
{
var builder = chatClient.AsBuilder();
if (originalFactory is not null)
{
builder.Use(originalFactory);
}
return builder.ConfigureOptions(co
=> co.Tools = co.Tools?.Select(tool => tool is AIFunction aiFunction
? new MiddlewareEnabledFunction(this.InnerAgent, aiFunction, this._delegateFunc)
: tool)
.ToList())
.Build();
};
return options;
}
private sealed class MiddlewareEnabledFunction(AIAgent innerAgent, AIFunction innerFunction, Func>, CancellationToken, ValueTask> next) : DelegatingAIFunction(innerFunction)
{
protected override async ValueTask InvokeCoreAsync(AIFunctionArguments arguments, CancellationToken cancellationToken)
{
var context = FunctionInvokingChatClient.CurrentContext
?? new FunctionInvocationContext() // When there is no ambient context, create a new one to hold the arguments
{
Arguments = arguments,
Function = this.InnerFunction,
CallContent = new(string.Empty, this.InnerFunction.Name, new Dictionary(arguments)),
};
return await next(innerAgent, context, CoreLogicAsync, cancellationToken).ConfigureAwait(false);
ValueTask CoreLogicAsync(FunctionInvocationContext ctx, CancellationToken cancellationToken)
=> base.InvokeCoreAsync(ctx.Arguments, cancellationToken);
}
}
}