diff --git a/dotnet/src/Agents/Abstractions/Definition/AgentDefinition.cs b/dotnet/src/Agents/Abstractions/Definition/AgentDefinition.cs index 10e01190c551..ae0c3a8411dc 100644 --- a/dotnet/src/Agents/Abstractions/Definition/AgentDefinition.cs +++ b/dotnet/src/Agents/Abstractions/Definition/AgentDefinition.cs @@ -8,7 +8,6 @@ namespace Microsoft.SemanticKernel.Agents; /// /// Defines an agent. /// -[ExcludeFromCodeCoverage] [Experimental("SKEXP0110")] public sealed class AgentDefinition { diff --git a/dotnet/src/Agents/Abstractions/Definition/AgentFactory.cs b/dotnet/src/Agents/Abstractions/Definition/AgentFactory.cs index bf1f7753cbf8..e4da560233d7 100644 --- a/dotnet/src/Agents/Abstractions/Definition/AgentFactory.cs +++ b/dotnet/src/Agents/Abstractions/Definition/AgentFactory.cs @@ -12,7 +12,6 @@ namespace Microsoft.SemanticKernel.Agents; /// /// Represents a factory for creating instances. /// -[ExcludeFromCodeCoverage] [Experimental("SKEXP0110")] public abstract class AgentFactory { diff --git a/dotnet/src/Agents/Abstractions/Definition/AgentInput.cs b/dotnet/src/Agents/Abstractions/Definition/AgentInput.cs index 3a71e60fea9d..04a910636a22 100644 --- a/dotnet/src/Agents/Abstractions/Definition/AgentInput.cs +++ b/dotnet/src/Agents/Abstractions/Definition/AgentInput.cs @@ -7,7 +7,6 @@ namespace Microsoft.SemanticKernel.Agents; /// /// Represents an input for an agent. /// -[ExcludeFromCodeCoverage] [Experimental("SKEXP0110")] public sealed class AgentInput { diff --git a/dotnet/src/Agents/Abstractions/Definition/AgentMetadata.cs b/dotnet/src/Agents/Abstractions/Definition/AgentMetadata.cs index 69eeaea9fea3..7112602be8dc 100644 --- a/dotnet/src/Agents/Abstractions/Definition/AgentMetadata.cs +++ b/dotnet/src/Agents/Abstractions/Definition/AgentMetadata.cs @@ -9,7 +9,6 @@ namespace Microsoft.SemanticKernel.Agents; /// /// Defines agent metadata. /// -[ExcludeFromCodeCoverage] [Experimental("SKEXP0110")] public sealed class AgentMetadata { diff --git a/dotnet/src/Agents/Abstractions/Definition/AgentOutput.cs b/dotnet/src/Agents/Abstractions/Definition/AgentOutput.cs index e6131bd9acf9..95935edb9639 100644 --- a/dotnet/src/Agents/Abstractions/Definition/AgentOutput.cs +++ b/dotnet/src/Agents/Abstractions/Definition/AgentOutput.cs @@ -7,7 +7,6 @@ namespace Microsoft.SemanticKernel.Agents; /// /// Represents an output for an agent. /// -[ExcludeFromCodeCoverage] [Experimental("SKEXP0110")] public sealed class AgentOutput { diff --git a/dotnet/src/Agents/Abstractions/Definition/AgentToolDefinition.cs b/dotnet/src/Agents/Abstractions/Definition/AgentToolDefinition.cs index 6d0e368b355e..5617d476ee13 100644 --- a/dotnet/src/Agents/Abstractions/Definition/AgentToolDefinition.cs +++ b/dotnet/src/Agents/Abstractions/Definition/AgentToolDefinition.cs @@ -9,7 +9,6 @@ namespace Microsoft.SemanticKernel.Agents; /// /// The options for defining a tool. /// -[ExcludeFromCodeCoverage] [Experimental("SKEXP0110")] public sealed class AgentToolDefinition { diff --git a/dotnet/src/Agents/Abstractions/Definition/ModelConnection.cs b/dotnet/src/Agents/Abstractions/Definition/ModelConnection.cs index f97e9d7823ef..2390e985cc45 100644 --- a/dotnet/src/Agents/Abstractions/Definition/ModelConnection.cs +++ b/dotnet/src/Agents/Abstractions/Definition/ModelConnection.cs @@ -9,7 +9,6 @@ namespace Microsoft.SemanticKernel.Agents; /// /// Defines the connection for a model. /// -[ExcludeFromCodeCoverage] [Experimental("SKEXP0110")] public sealed class ModelConnection { diff --git a/dotnet/src/Agents/Abstractions/Definition/ModelDefinition.cs b/dotnet/src/Agents/Abstractions/Definition/ModelDefinition.cs index ee5db8edd3f8..92fb7460ae25 100644 --- a/dotnet/src/Agents/Abstractions/Definition/ModelDefinition.cs +++ b/dotnet/src/Agents/Abstractions/Definition/ModelDefinition.cs @@ -8,7 +8,6 @@ namespace Microsoft.SemanticKernel.Agents; /// /// Defines the model to be used by an agent. /// -[ExcludeFromCodeCoverage] [Experimental("SKEXP0110")] public sealed class ModelDefinition { @@ -30,7 +29,7 @@ public string Api get => this._api ?? DefaultApi; set { - Verify.NotNull(value); + Verify.NotNullOrWhiteSpace(value); this._api = value; } } diff --git a/dotnet/src/Agents/Abstractions/Extensions/AgentDefinitionExtensions.cs b/dotnet/src/Agents/Abstractions/Extensions/AgentDefinitionExtensions.cs index 5ef0e1b8a6b1..20bf75e32f46 100644 --- a/dotnet/src/Agents/Abstractions/Extensions/AgentDefinitionExtensions.cs +++ b/dotnet/src/Agents/Abstractions/Extensions/AgentDefinitionExtensions.cs @@ -23,6 +23,7 @@ public static class AgentDefinitionExtensions public static KernelArguments GetDefaultKernelArguments(this AgentDefinition agentDefinition, Kernel kernel) { Verify.NotNull(agentDefinition); + Verify.NotNull(kernel); PromptExecutionSettings executionSettings = new() { @@ -39,7 +40,7 @@ public static KernelArguments GetDefaultKernelArguments(this AgentDefinition age var nameParts = FunctionName.Parse(function.Id!, FunctionNameSeparator); // Look up the function in the kernel. - if (kernel is not null && kernel.Plugins.TryGetFunction(nameParts.PluginName, nameParts.Name, out var kernelFunction)) + if (kernel.Plugins.TryGetFunction(nameParts.PluginName, nameParts.Name, out var kernelFunction)) { kernelFunctions.Add(kernelFunction); continue; diff --git a/dotnet/src/Agents/UnitTests/Definition/AgentDefinitionModelTests.cs b/dotnet/src/Agents/UnitTests/Definition/AgentDefinitionModelTests.cs new file mode 100644 index 000000000000..d9ffc8385520 --- /dev/null +++ b/dotnet/src/Agents/UnitTests/Definition/AgentDefinitionModelTests.cs @@ -0,0 +1,27 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System; +using Microsoft.SemanticKernel.Agents; +using Xunit; + +namespace SemanticKernel.Agents.UnitTests.Definition; + +/// +/// Unit testing of and related model classes. +/// +public class AgentDefinitionModelTests +{ + /// + /// Verify ModelDefinition.Api cannot be null or whitespace. + /// + [Fact] + public void VerifyModelDefinitionApiNotNullOrWhiteSpace() + { + // Arrange + var modelDefinition = new ModelDefinition(); + + // Act & Assert + Assert.Throws(() => modelDefinition.Api = ""); + Assert.Throws(() => modelDefinition.Api = null!); + } +} diff --git a/dotnet/src/Agents/UnitTests/Extensions/AgentDefinitionExtensionsTests.cs b/dotnet/src/Agents/UnitTests/Extensions/AgentDefinitionExtensionsTests.cs new file mode 100644 index 000000000000..4490589b4d6c --- /dev/null +++ b/dotnet/src/Agents/UnitTests/Extensions/AgentDefinitionExtensionsTests.cs @@ -0,0 +1,200 @@ +// Copyright (c) Microsoft. All rights reserved. +using System; +using System.ComponentModel; +using System.Linq; +using Microsoft.SemanticKernel; +using Microsoft.SemanticKernel.Agents; +using Xunit; + +namespace SemanticKernel.Agents.UnitTests.Extensions; + +/// +/// Unit testing of . +/// +public class AgentDefinitionExtensionsTests +{ + /// + /// Verify default instance of can be created. + /// + [Fact] + public void VerifyGetDefaultKernelArguments() + { + // Arrange + var agentDefinition = new AgentDefinition(); + var kernel = new Kernel(); + + // Act + var kernelArguments = agentDefinition.GetDefaultKernelArguments(kernel); + + // Assert + Assert.NotNull(kernelArguments); + } + + /// + /// Verify default instance of has function calling enabled. + /// + [Fact] + public void VerifyGetDefaultKernelArgumentsEnablesFunctionCalling() + { + // Arrange + var agentDefinition = new AgentDefinition + { + Tools = [new() { Type = "function", Id = "MyPlugin.Function1" }] + }; + var kernel = new Kernel(); + var kernelPlugin = kernel.Plugins.AddFromType(); + + // Act + var kernelArguments = agentDefinition.GetDefaultKernelArguments(kernel); + + // Assert + Assert.NotNull(kernelArguments); + Assert.NotNull(kernelArguments.ExecutionSettings); + Assert.Single(kernelArguments.ExecutionSettings); + Assert.NotNull(kernelArguments.ExecutionSettings["default"].FunctionChoiceBehavior); + var autoFunctionChoiceBehavior = kernelArguments.ExecutionSettings["default"].FunctionChoiceBehavior as AutoFunctionChoiceBehavior; + Assert.NotNull(autoFunctionChoiceBehavior); + Assert.NotNull(autoFunctionChoiceBehavior.Functions); + Assert.Single(autoFunctionChoiceBehavior.Functions); + } + + /// + /// Verify instance of cannot be created if function is not available. + /// + [Fact] + public void VerifyGetDefaultKernelArgumentsThrowsForInvalidFunction() + { + // Arrange + var agentDefinition = new AgentDefinition + { + Tools = [new() { Type = "function", Id = "MyPlugin.Function2" }] + }; + var kernel = new Kernel(); + var kernelPlugin = kernel.Plugins.AddFromType(); + + // Act & Assert + Assert.Throws(() => agentDefinition.GetDefaultKernelArguments(kernel)); + } + + /// + /// Verify GetPromptTemplate returns null if there is no template factory, template or instructions. + /// + [Fact] + public void VerifyGetPromptTemplateReturnsNull() + { + // Arrange + var agentDefinition = new AgentDefinition(); + var kernel = new Kernel(); + + // Act & Assert + Assert.Null(agentDefinition.GetPromptTemplate(kernel, null)); + } + + /// + /// Verify GetPromptTemplate returns null if there is no template factory, template or instructions. + /// + [Fact] + public void VerifyGetPromptTemplate() + { + // Arrange + var agentDefinition = new AgentDefinition() + { + Instructions = "instructions", + Template = new() { Format = "semantic-kernel" } + }; + var kernel = new Kernel(); + var templateFactory = new KernelPromptTemplateFactory(); + + // Act + var promptTemplate = agentDefinition.GetPromptTemplate(kernel, templateFactory); + + // Assert + Assert.NotNull(promptTemplate); + } + + /// + /// Verify GetFirstToolDefinition returns the correct tool. + /// + [Fact] + public void VerifyGetFirstToolDefinition() + { + // Arrange + var agentDefinition = new AgentDefinition + { + Tools = + [ + new() { Type = "function", Id = "MyPlugin.Function1" }, + new() { Type = "code_interpreter" } + ] + }; + var kernel = new Kernel(); + var kernelPlugin = kernel.Plugins.AddFromType(); + + // Act + var toolDefinition = agentDefinition.GetFirstToolDefinition("function"); + + // Assert + Assert.NotNull(toolDefinition); + Assert.Equal("function", toolDefinition.Type); + } + + /// + /// Verify GetToolDefinitions returns the correct tools. + /// + [Fact] + public void VerifyGetToolDefinitions() + { + // Arrange + var agentDefinition = new AgentDefinition + { + Tools = + [ + new() { Type = "function", Id = "MyPlugin.Function1" }, + new() { Type = "function", Id = "MyPlugin.Function2" }, + new() { Type = "code_interpreter" } + ] + }; + var kernel = new Kernel(); + var kernelPlugin = kernel.Plugins.AddFromType(); + + // Act + var toolDefinitions = agentDefinition.GetToolDefinitions("function"); + + // Assert + Assert.NotNull(toolDefinitions); + Assert.Equal(2, toolDefinitions.Count()); + } + + /// + /// Verify HasToolType returns the correct values. + /// + [Fact] + public void VerifyHasToolType() + { + // Arrange + var agentDefinition = new AgentDefinition + { + Tools = + [ + new() { Type = "function", Id = "MyPlugin.Function1" }, + new() { Type = "code_interpreter" } + ] + }; + var kernel = new Kernel(); + var kernelPlugin = kernel.Plugins.AddFromType(); + + // Act & Assert + Assert.True(agentDefinition.HasToolType("function")); + Assert.True(agentDefinition.HasToolType("code_interpreter")); + Assert.False(agentDefinition.HasToolType("file_search")); + } + + #region private + private sealed class MyPlugin + { + [KernelFunction("Function1")] + [Description("Description for function 1.")] + public string Function1([Description("Description for parameter 1")] string param1) => $"Function1: {param1}"; + } + #endregion +} diff --git a/dotnet/src/Agents/UnitTests/Extensions/AgentToolDefinitionExtensionsTests.cs b/dotnet/src/Agents/UnitTests/Extensions/AgentToolDefinitionExtensionsTests.cs new file mode 100644 index 000000000000..98d3d9f38263 --- /dev/null +++ b/dotnet/src/Agents/UnitTests/Extensions/AgentToolDefinitionExtensionsTests.cs @@ -0,0 +1,42 @@ +// Copyright (c) Microsoft. All rights reserved. +using System; +using System.Collections.Generic; +using Microsoft.SemanticKernel.Agents; +using Xunit; + +namespace SemanticKernel.Agents.UnitTests.Extensions; + +/// +/// Unit testing of . +/// +public class AgentToolDefinitionExtensionsTests +{ + /// + /// Verify GetOption. + /// + [Fact] + public void VerifyGetOption() + { + // Arrange + var agentToolDefinition = new AgentToolDefinition() + { + Type = "function", + Id = "MyPlugin.Function1", + Options = new Dictionary() + { + { "null", null }, + { "string", "string" }, + { "int", 1 }, + { "array", new string[] { "1", "2", "3" } }, + } + }; + + // Act & Assert + Assert.Null(agentToolDefinition.GetOption("null")); + Assert.Equal("string", agentToolDefinition.GetOption("string")); + Assert.Equal(1, agentToolDefinition.GetOption("int")); + Assert.Equal(new string[] { "1", "2", "3" }, agentToolDefinition.GetOption("array")); + Assert.Throws(() => agentToolDefinition.GetOption("string")); + Assert.Throws(() => agentToolDefinition.GetOption(null!)); + } +} diff --git a/dotnet/src/Agents/UnitTests/Yaml/AgentDefinitionYamlTests.cs b/dotnet/src/Agents/UnitTests/Yaml/AgentDefinitionYamlTests.cs index 89067749b080..c5f25c902b74 100644 --- a/dotnet/src/Agents/UnitTests/Yaml/AgentDefinitionYamlTests.cs +++ b/dotnet/src/Agents/UnitTests/Yaml/AgentDefinitionYamlTests.cs @@ -21,6 +21,7 @@ public void VerifyAgentDefinitionFromYaml() // Arrange var text = """ + id: agent_12345 version: 1.0.0 type: chat_completion_agent name: My Agent @@ -63,8 +64,10 @@ public void VerifyAgentDefinitionFromYaml() tools: - id: tool1 type: code_interpreter + description: Code interpreter tool - id: tool2 type: file_search + description: File search tool """; // Act diff --git a/dotnet/src/Agents/UnitTests/Yaml/KernelAgentYamlTests.cs b/dotnet/src/Agents/UnitTests/Yaml/AgentYamlTests.cs similarity index 98% rename from dotnet/src/Agents/UnitTests/Yaml/KernelAgentYamlTests.cs rename to dotnet/src/Agents/UnitTests/Yaml/AgentYamlTests.cs index 4a6dcf984063..d8ba153c707c 100644 --- a/dotnet/src/Agents/UnitTests/Yaml/KernelAgentYamlTests.cs +++ b/dotnet/src/Agents/UnitTests/Yaml/AgentYamlTests.cs @@ -21,9 +21,9 @@ namespace SemanticKernel.Agents.UnitTests.Yaml; /// -/// Unit tests for . +/// Unit tests for . /// -public class KernelAgentYamlTests : IDisposable +public class AgentYamlTests : IDisposable { private readonly HttpMessageHandlerStub _messageHandlerStub; private readonly HttpClient _httpClient; @@ -32,7 +32,7 @@ public class KernelAgentYamlTests : IDisposable /// /// Initializes a new instance of the class. /// - public KernelAgentYamlTests() + public AgentYamlTests() { this._messageHandlerStub = new HttpMessageHandlerStub(); this._httpClient = new HttpClient(this._messageHandlerStub, disposeHandler: false); diff --git a/dotnet/src/Agents/UnitTests/Yaml/AzureAIKernelAgentYamlTests.cs b/dotnet/src/Agents/UnitTests/Yaml/AzureAIKernelAgentYamlTests.cs index 5cd09dd92c2d..5a9ff15f6fa7 100644 --- a/dotnet/src/Agents/UnitTests/Yaml/AzureAIKernelAgentYamlTests.cs +++ b/dotnet/src/Agents/UnitTests/Yaml/AzureAIKernelAgentYamlTests.cs @@ -19,7 +19,7 @@ namespace SemanticKernel.Agents.UnitTests.Yaml; /// -/// Unit tests for with . +/// Unit tests for with . /// public class AzureAIKernelAgentYamlTests : IDisposable { diff --git a/dotnet/src/Agents/Yaml/AgentToolDefinitionTypeConverter.cs b/dotnet/src/Agents/Yaml/AgentToolDefinitionTypeConverter.cs deleted file mode 100644 index 65c337fce4cf..000000000000 --- a/dotnet/src/Agents/Yaml/AgentToolDefinitionTypeConverter.cs +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System; -using System.Collections.Generic; -using YamlDotNet.Core; -using YamlDotNet.Core.Events; -using YamlDotNet.Serialization; -using YamlDotNet.Serialization.NamingConventions; - -namespace Microsoft.SemanticKernel.Agents; - -/// -/// Type converter custom deserialization for from YAML. -/// -/// -/// Required to correctly deserialize the from YAML. -/// -internal sealed class AgentToolDefinitionTypeConverter : IYamlTypeConverter -{ - /// - public bool Accepts(Type type) - { - return type == typeof(AgentToolDefinition); - } - - /// - public object? ReadYaml(IParser parser, Type type) - { - s_deserializer ??= new DeserializerBuilder() - .WithNamingConvention(UnderscoredNamingConvention.Instance) - .IgnoreUnmatchedProperties() // Required to ignore the 'type' property used as type discrimination. Otherwise, the "Property 'type' not found on type '{type.FullName}'" exception is thrown. - .Build(); - - parser.MoveNext(); // Move to the first property - - var agentToolDefinition = new AgentToolDefinition(); - while (parser.Current is not MappingEnd) - { - var propertyName = parser.Consume().Value; - switch (propertyName) - { - case "type": - agentToolDefinition.Type = s_deserializer.Deserialize(parser); - break; - case "name": - agentToolDefinition.Id = s_deserializer.Deserialize(parser); - break; - case "description": - agentToolDefinition.Description = s_deserializer.Deserialize(parser); - break; - default: - (agentToolDefinition.Options ??= new Dictionary()).Add(propertyName, s_deserializer.Deserialize(parser)); - break; - } - } - parser.MoveNext(); // Move past the MappingEnd event - return agentToolDefinition; - } - - /// - public void WriteYaml(IEmitter emitter, object? value, Type type) - { - throw new NotImplementedException(); - } - - /// - /// The YamlDotNet deserializer instance. - /// - private static IDeserializer? s_deserializer; -} diff --git a/dotnet/src/Agents/Yaml/Extensions/YamlKernelAgentFactoryExtensions.cs b/dotnet/src/Agents/Yaml/Extensions/YamlAgentFactoryExtensions.cs similarity index 96% rename from dotnet/src/Agents/Yaml/Extensions/YamlKernelAgentFactoryExtensions.cs rename to dotnet/src/Agents/Yaml/Extensions/YamlAgentFactoryExtensions.cs index 0edb5bf0dc9c..22c2c8b3a8bd 100644 --- a/dotnet/src/Agents/Yaml/Extensions/YamlKernelAgentFactoryExtensions.cs +++ b/dotnet/src/Agents/Yaml/Extensions/YamlAgentFactoryExtensions.cs @@ -9,7 +9,7 @@ namespace Microsoft.SemanticKernel.Agents; /// /// Extension methods for to create agents from YAML. /// -public static class YamlKernelAgentFactoryExtensions +public static class YamlAgentFactoryExtensions { /// /// Create a from the given YAML text.