mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
892c177e93
When converting base AgentRunOptions to ChatClientAgentRunOptions, the middleware now preserves AllowBackgroundResponses, ContinuationToken, and AdditionalProperties in addition to ResponseFormat. Added unit test verifying all properties are preserved during the conversion. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
89 lines
4.2 KiB
C#
89 lines
4.2 KiB
C#
// 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;
|
|
|
|
/// <summary>
|
|
/// Internal agent decorator that adds function invocation middleware logic.
|
|
/// </summary>
|
|
internal sealed class FunctionInvocationDelegatingAgent : DelegatingAIAgent
|
|
{
|
|
private readonly Func<AIAgent, FunctionInvocationContext, Func<FunctionInvocationContext, CancellationToken, ValueTask<object?>>, CancellationToken, ValueTask<object?>> _delegateFunc;
|
|
|
|
internal FunctionInvocationDelegatingAgent(AIAgent innerAgent, Func<AIAgent, FunctionInvocationContext, Func<FunctionInvocationContext, CancellationToken, ValueTask<object?>>, CancellationToken, ValueTask<object?>> delegateFunc) : base(innerAgent)
|
|
{
|
|
this._delegateFunc = delegateFunc;
|
|
}
|
|
|
|
protected override Task<AgentResponse> RunCoreAsync(IEnumerable<ChatMessage> messages, AgentSession? session = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default)
|
|
=> this.InnerAgent.RunAsync(messages, session, this.AgentRunOptionsWithFunctionMiddleware(options), cancellationToken);
|
|
|
|
protected override IAsyncEnumerable<AgentResponseUpdate> RunCoreStreamingAsync(IEnumerable<ChatMessage> 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<AIAgent, FunctionInvocationContext, Func<FunctionInvocationContext, CancellationToken, ValueTask<object?>>, CancellationToken, ValueTask<object?>> next) : DelegatingAIFunction(innerFunction)
|
|
{
|
|
protected override async ValueTask<object?> 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<string, object?>(arguments)),
|
|
};
|
|
|
|
return await next(innerAgent, context, CoreLogicAsync, cancellationToken).ConfigureAwait(false);
|
|
|
|
ValueTask<object?> CoreLogicAsync(FunctionInvocationContext ctx, CancellationToken cancellationToken)
|
|
=> base.InvokeCoreAsync(ctx.Arguments, cancellationToken);
|
|
}
|
|
}
|
|
}
|