Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
46 changes: 38 additions & 8 deletions dotnet/src/Agents/Orchestration/Handoff/HandoffActor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents.Runtime;
using Microsoft.SemanticKernel.Agents.Runtime.Core;
using Microsoft.SemanticKernel.ChatCompletion;
Expand Down Expand Up @@ -66,18 +67,47 @@ protected override AgentInvokeOptions CreateInvokeOptions(Func<ChatMessageConten
kernel.AutoFunctionInvocationFilters.Add(new HandoffInvocationFilter());
kernel.Plugins.Add(this.CreateHandoffPlugin());

// Create invocation options that use auto-function invocation and our modified kernel.
// Preserve agent execution settings while enabling auto function choice for handoff plugins.
FunctionChoiceBehavior handoffFunctionChoice =
FunctionChoiceBehavior.Auto(options: new()
{
RetainArgumentTypes = true,
});

Dictionary<string, PromptExecutionSettings> executionSettings = [];
if (this.Agent.Arguments?.ExecutionSettings is { Count: > 0 } agentExecutionSettings)
{
foreach (KeyValuePair<string, PromptExecutionSettings> kv in agentExecutionSettings)
{
PromptExecutionSettings cloned = kv.Value.Clone();
cloned.FunctionChoiceBehavior = handoffFunctionChoice;
executionSettings[kv.Key] = cloned;
}
}
else
{
executionSettings[PromptExecutionSettings.DefaultServiceId] = new PromptExecutionSettings
{
FunctionChoiceBehavior = handoffFunctionChoice,
};
}

Dictionary<string, object?> parameters = [];
if (this.Agent.Arguments is { Count: > 0 } agentArguments)
{
foreach (KeyValuePair<string, object?> kv in agentArguments)
{
parameters[kv.Key] = kv.Value;
}
}

KernelArguments kernelArguments = new(parameters, executionSettings);

AgentInvokeOptions options =
new()
{
Kernel = kernel,
KernelArguments = new(new PromptExecutionSettings
{
FunctionChoiceBehavior = FunctionChoiceBehavior.Auto(options: new()
{
RetainArgumentTypes = true,
})
}),
KernelArguments = kernelArguments,
OnIntermediateMessage = messageHandler,
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,15 @@
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
using Microsoft.SemanticKernel.Agents.Orchestration;
using Microsoft.SemanticKernel.Agents.Orchestration.Handoff;
using Microsoft.SemanticKernel.Agents.Runtime.InProcess;
using Microsoft.SemanticKernel.Connectors.OpenAI;
using ChatReasoningEffortLevel = global::OpenAI.Chat.ChatReasoningEffortLevel;
using Xunit;

namespace SemanticKernel.Agents.UnitTests.Orchestration;
Expand Down Expand Up @@ -88,6 +91,32 @@ public async Task HandoffOrchestrationWithMultipleAgentsAsync()
Assert.Equal("Final response", response);
}

[Fact]
public async Task HandoffOrchestrationPreservesAgentExecutionSettingsAsync()
{
// Arrange
HttpMessageHandlerStub messageHandlerStub = new();
ChatCompletionAgent mockAgent =
this.CreateMockAgent(
"Agent1",
"Test Agent",
Responses.Message("Final response"),
messageHandlerStub,
new KernelArguments(new OpenAIPromptExecutionSettings
{
ReasoningEffort = ChatReasoningEffortLevel.High,
}));

// Act
string response = await ExecuteOrchestrationAsync(OrchestrationHandoffs.StartWith(mockAgent), mockAgent);

// Assert
Assert.Equal("Final response", response);

using JsonDocument requestBody = JsonDocument.Parse(messageHandlerStub.RequestContent!);
Assert.Equal("high", requestBody.RootElement.GetProperty("reasoning_effort").GetString());
}

private static async Task<string> ExecuteOrchestrationAsync(OrchestrationHandoffs handoffs, params Agent[] mockAgents)
{
// Arrange
Expand All @@ -110,17 +139,15 @@ private static async Task<string> ExecuteOrchestrationAsync(OrchestrationHandoff
return response;
}

private ChatCompletionAgent CreateMockAgent(string name, string description, string response)
private ChatCompletionAgent CreateMockAgent(string name, string description, string response, HttpMessageHandlerStub? messageHandlerStub = null, KernelArguments? arguments = null)
{
HttpMessageHandlerStub messageHandlerStub =
new()
{
ResponseToReturn = new HttpResponseMessage
{
StatusCode = System.Net.HttpStatusCode.OK,
Content = new StringContent(response),
},
};
messageHandlerStub ??= new HttpMessageHandlerStub();

messageHandlerStub.ResponseToReturn = new HttpResponseMessage
{
StatusCode = System.Net.HttpStatusCode.OK,
Content = new StringContent(response),
};
HttpClient httpClient = new(messageHandlerStub, disposeHandler: false);

this._disposables.Add(messageHandlerStub);
Expand All @@ -136,6 +163,7 @@ private ChatCompletionAgent CreateMockAgent(string name, string description, str
Name = name,
Description = description,
Kernel = kernel,
Arguments = arguments,
};

return mockAgent1;
Expand Down
Loading