Skip to content

Commit

Permalink
.Net Agents: Fix consumption of execution settings when defining agen…
Browse files Browse the repository at this point in the history
…t from template. (#10519)

### Motivation and Context
<!-- Thank you for your contribution to the semantic-kernel repo!
Please help reviewers and future users, providing the following
information:
  1. Why is this change required?
  2. What problem does it solve?
  3. What scenario does it contribute to?
  4. If it fixes an open issue, please link to the issue here.
-->

When creating `ChatCompletionAgent` from a YAML resource template, the
execution-settings are not realized.

### Description
<!-- Describe your changes, the overall approach, the underlying design.
These notes will help understanding how your code works. Thanks! -->

- Include the execution settings when creating a `ChatCompletionAgent`
from a YAML resource template.
- Enforce never-null `KernelArguments` property on `KernelAgent`.
- Updated samples
- Added unit-tests

### Contribution Checklist
<!-- Before submitting this PR, please make sure: -->

- [X] The code builds clean without any errors or warnings
- [X] The PR follows the [SK Contribution
Guidelines](https://github.com/microsoft/semantic-kernel/blob/main/CONTRIBUTING.md)
and the [pre-submission formatting
script](https://github.com/microsoft/semantic-kernel/blob/main/CONTRIBUTING.md#development-scripts)
raises no violations
- [X] All unit tests pass, and I have added new tests where possible
- [X] I didn't break anyone 😄
  • Loading branch information
crickman authored Feb 13, 2025
1 parent 5486f2b commit ca052ee
Show file tree
Hide file tree
Showing 10 changed files with 98 additions and 27 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public async Task UseTemplateForAzureAgentAsync()
AgentsClient client = clientProvider.Client.GetAgentsClient();
Agent definition = await client.CreateAgentAsync("gpt-4o", templateConfig.Name, templateConfig.Description, templateConfig.Template);
// Instructions, Name and Description properties defined via the config.
AzureAIAgent agent = new(definition, clientProvider, new KernelPromptTemplateFactory())
AzureAIAgent agent = new(definition, clientProvider)
{
Kernel = new Kernel(),
Arguments = new KernelArguments()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
name: ToolAgent
template_format: semantic-kernel
description: An agent that is configured to auto-invoke plugins.
execution_settings:
default:
function_choice_behavior:
type: auto
12 changes: 6 additions & 6 deletions dotnet/samples/GettingStartedWithAgents/Step01_Agent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,14 +62,14 @@ public async Task UseTemplateForChatCompletionAgentAsync()

// Instructions, Name and Description properties defined via the config.
ChatCompletionAgent agent =
new(templateConfig, new KernelPromptTemplateFactory())
new(templateConfig)
{
Kernel = this.CreateKernelWithChatCompletion(),
Arguments = new KernelArguments()
{
{ "topic", "Dog" },
{ "length", "3" },
}
Arguments =
{
{ "topic", "Dog" },
{ "length", "3" },
}
};

/// Create the chat history to capture the agent interaction.
Expand Down
25 changes: 25 additions & 0 deletions dotnet/samples/GettingStartedWithAgents/Step02_Plugins.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using Microsoft.SemanticKernel.ChatCompletion;
using Microsoft.SemanticKernel.Connectors.OpenAI;
using Plugins;
using Resources;

namespace GettingStarted;

Expand Down Expand Up @@ -46,6 +47,30 @@ public async Task UseChatCompletionWithPluginEnumParameterAsync()
await InvokeAgentAsync(agent, chat, "Create a beautiful red colored widget for me.");
}

[Fact]
public async Task UseChatCompletionWithTemplateExecutionSettingsAsync()
{
// Read the template resource
string autoInvokeYaml = EmbeddedResource.Read("AutoInvokeTools.yaml");
PromptTemplateConfig templateConfig = KernelFunctionYaml.ToPromptTemplateConfig(autoInvokeYaml);

// Define the agent:
// Execution-settings with auto-invocation of plubins defined via the config.
ChatCompletionAgent agent =
new(templateConfig)
{
Kernel = this.CreateKernelWithChatCompletion()
};

agent.Kernel.Plugins.AddFromType<WidgetFactory>();

/// Create the chat history to capture the agent interaction.
ChatHistory chat = [];

// Respond to user input, invoking functions where appropriate.
await InvokeAgentAsync(agent, chat, "Create a beautiful red colored widget for me.");
}

private ChatCompletionAgent CreateAgentWithPlugin(
KernelPlugin plugin,
string? instructions = null,
Expand Down
10 changes: 2 additions & 8 deletions dotnet/src/Agents/Abstractions/KernelAgent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public abstract class KernelAgent : Agent
/// <remarks>
/// Also includes <see cref="PromptExecutionSettings"/>.
/// </remarks>
public KernelArguments? Arguments { get; init; }
public KernelArguments Arguments { get; init; } = [];

/// <summary>
/// Gets the instructions for the agent (optional).
Expand Down Expand Up @@ -73,14 +73,8 @@ public abstract class KernelAgent : Agent
/// It allows for incremental addition or replacement of specific parameters while also preserving the ability
/// to override the execution settings.
/// </remarks>
protected KernelArguments? MergeArguments(KernelArguments? arguments)
protected KernelArguments MergeArguments(KernelArguments? arguments)
{
// Avoid merge when default arguments are not set.
if (this.Arguments == null)
{
return arguments;
}

// Avoid merge when override arguments are not set.
if (arguments == null)
{
Expand Down
1 change: 1 addition & 0 deletions dotnet/src/Agents/Core/ChatCompletionAgent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ public ChatCompletionAgent(
this.Name = templateConfig.Name;
this.Description = templateConfig.Description;
this.Instructions = templateConfig.Template;
this.Arguments = new(templateConfig.ExecutionSettings.Values);
this.Template = templateFactory?.Create(templateConfig);
}

Expand Down
6 changes: 3 additions & 3 deletions dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ public static async Task<OpenAIAssistantAgent> CreateAsync(
new OpenAIAssistantAgent(model, clientProvider, client)
{
Kernel = kernel,
Arguments = defaultArguments
Arguments = defaultArguments ?? [],
};
}

Expand Down Expand Up @@ -176,7 +176,7 @@ public static async Task<OpenAIAssistantAgent> CreateAsync(
new OpenAIAssistantAgent(model, clientProvider, client)
{
Kernel = kernel,
Arguments = defaultArguments
Arguments = defaultArguments ?? [],
};
}

Expand Down Expand Up @@ -240,7 +240,7 @@ public static async Task<OpenAIAssistantAgent> RetrieveAsync(
new OpenAIAssistantAgent(model, clientProvider, client)
{
Kernel = kernel,
Arguments = defaultArguments,
Arguments = defaultArguments ?? [],
Template = template,
};
}
Expand Down
47 changes: 46 additions & 1 deletion dotnet/src/Agents/UnitTests/Core/ChatCompletionAgentTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,52 @@ public void VerifyChatCompletionAgentDefinition()
Assert.Equal("test instructions", agent.Instructions);
Assert.Equal("test description", agent.Description);
Assert.Equal("test name", agent.Name);
Assert.Null(agent.Arguments);
Assert.NotNull(agent.Arguments);
}

/// <summary>
/// Verify the invocation and response of <see cref="ChatCompletionAgent"/>.
/// </summary>
[Fact]
public void VerifyChatCompletionAgentTemplate()
{
PromptTemplateConfig config =
new()
{
Name = "TestName",
Description = "TestDescription",
Template = "TestInstructions",
ExecutionSettings =
{
{
PromptExecutionSettings.DefaultServiceId,
new PromptExecutionSettings()
{
FunctionChoiceBehavior = FunctionChoiceBehavior.Auto(),
ModelId = "gpt-new",
}
},
{
"manual",
new PromptExecutionSettings()
{
ServiceId = "manual",
FunctionChoiceBehavior = FunctionChoiceBehavior.Required(),
ModelId = "gpt-old",
}
},
}
};

// Arrange
ChatCompletionAgent agent = new(config);

// Assert
Assert.NotNull(agent.Id);
Assert.Equal(config.Template, agent.Instructions);
Assert.Equal(config.Description, agent.Description);
Assert.Equal(config.Name, agent.Name);
Assert.Equal(config.ExecutionSettings, agent.Arguments.ExecutionSettings);
}

/// <summary>
Expand Down
13 changes: 6 additions & 7 deletions dotnet/src/Agents/UnitTests/KernelAgentTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,18 @@ public class KernelAgentTests
public void VerifyNullArgumentMerge()
{
// Arrange
MockAgent agentWithNullArguments = new();
MockAgent agentWithNoArguments = new();
// Act
KernelArguments? arguments = agentWithNullArguments.MergeArguments(null);
KernelArguments arguments = agentWithNoArguments.MergeArguments(null);
// Assert
Assert.Null(arguments);
Assert.Empty(arguments);

// Arrange
KernelArguments overrideArguments = [];
KernelArguments overrideArguments = new() { { "test", 1 } };
// Act
arguments = agentWithNullArguments.MergeArguments(overrideArguments);
arguments = agentWithNoArguments.MergeArguments(overrideArguments);
// Assert
Assert.NotNull(arguments);
Assert.StrictEqual(overrideArguments, arguments);
Assert.StrictEqual(1, arguments.Count);

// Arrange
MockAgent agentWithEmptyArguments = new() { Arguments = new() };
Expand Down
2 changes: 1 addition & 1 deletion dotnet/src/Agents/UnitTests/MockAgent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public override IAsyncEnumerable<StreamingChatMessageContent> InvokeStreamingAsy
}

// Expose protected method for testing
public new KernelArguments? MergeArguments(KernelArguments? arguments)
public new KernelArguments MergeArguments(KernelArguments? arguments)
{
return base.MergeArguments(arguments);
}
Expand Down

0 comments on commit ca052ee

Please sign in to comment.