// 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); } } }