From 0a5f070a5ce60b5e2b36ecf3adc5255fdb20609f Mon Sep 17 00:00:00 2001
From: markwallace-microsoft
<127216156+markwallace-microsoft@users.noreply.github.com>
Date: Mon, 24 Feb 2025 14:02:22 +0000
Subject: [PATCH 1/6] Add support for AzureAI tools
---
.../Definition/AgentToolDefinition.cs | 26 +++++++
.../Extensions/AgentDefinitionExtensions.cs | 68 ++++++++++++++++++-
.../AgentToolDefinitionExtensions.cs | 21 ++++++
3 files changed, 113 insertions(+), 2 deletions(-)
create mode 100644 dotnet/src/Agents/AzureAI/Extensions/AgentToolDefinitionExtensions.cs
diff --git a/dotnet/src/Agents/Abstractions/Definition/AgentToolDefinition.cs b/dotnet/src/Agents/Abstractions/Definition/AgentToolDefinition.cs
index 7c18ae624496..007daa8bb9fd 100644
--- a/dotnet/src/Agents/Abstractions/Definition/AgentToolDefinition.cs
+++ b/dotnet/src/Agents/Abstractions/Definition/AgentToolDefinition.cs
@@ -39,6 +39,19 @@ public string? Name
}
}
+ ///
+ /// The description of the tool.
+ ///
+ public string? Description
+ {
+ get => this._description;
+ set
+ {
+ Verify.NotNull(value);
+ this._description = value;
+ }
+ }
+
///
/// Gets or sets the configuration for the tool.
///
@@ -56,9 +69,22 @@ public IDictionary? Configuration
}
}
+ ///
+ /// Gets the required configuration.
+ ///
+ /// Name of the configuration value.
+ public object GetRequiredConfiguration(string key)
+ {
+ Verify.NotNull(key);
+ Verify.True(this.Configuration!.ContainsKey(key), $"The configuration key '{key}' is required.");
+
+ return this.Configuration[key];
+ }
+
#region private
private string? _type;
private string? _name;
+ private string? _description;
private IDictionary? _configuration;
#endregion
}
diff --git a/dotnet/src/Agents/AzureAI/Extensions/AgentDefinitionExtensions.cs b/dotnet/src/Agents/AzureAI/Extensions/AgentDefinitionExtensions.cs
index 362c103a16b7..5d1757fb976c 100644
--- a/dotnet/src/Agents/AzureAI/Extensions/AgentDefinitionExtensions.cs
+++ b/dotnet/src/Agents/AzureAI/Extensions/AgentDefinitionExtensions.cs
@@ -11,6 +11,16 @@ namespace Microsoft.SemanticKernel.Agents.AzureAI;
///
internal static class AgentDefinitionExtensions
{
+ private const string AzureAISearchType = "azure_aisearch";
+ private const string AzureFunctionType = "azure_function";
+ private const string BingGroundingType = "bing_grounding";
+ private const string CodeInterpreterType = "code_interpreter";
+ private const string FileSearchType = "file_search";
+ private const string FunctionToolType = "function_tool";
+ private const string MicrosoftFabricType = "microsoft_fabric";
+ private const string OpenApiType = "openapi";
+ private const string SharepointType = "sharepoint";
+
///
/// Return the Azure AI tool definitions which corresponds with the provided .
///
@@ -22,8 +32,15 @@ public static IEnumerable GetAzureToolDefinitions(this AgentDefi
{
return tool.Type switch
{
- "code_interpreter" => new CodeInterpreterToolDefinition(),
- "file_search" => new FileSearchToolDefinition(),
+ AzureAISearchType => CreateAzureAISearchToolDefinition(tool),
+ AzureFunctionType => CreateAzureFunctionToolDefinition(tool),
+ BingGroundingType => CreateBingGroundingToolDefinition(tool),
+ CodeInterpreterType => CreateCodeInterpreterToolDefinition(tool),
+ FileSearchType => CreateFileSearchToolDefinition(tool),
+ FunctionToolType => CreateFunctionToolDefinition(tool),
+ MicrosoftFabricType => CreateMicrosoftFabricToolDefinition(tool),
+ OpenApiType => CreateOpenApiToolDefinition(tool),
+ SharepointType => CreateSharepointToolDefinition(tool),
_ => throw new InvalidOperationException($"Unable to created Azure AI tool definition because of known tool type: {tool.Type}"),
};
});
@@ -40,4 +57,51 @@ public static IEnumerable GetAzureToolDefinitions(this AgentDefi
// TODO: Implement
return null;
}
+
+ #region private
+ private static AzureAISearchToolDefinition CreateAzureAISearchToolDefinition(AgentToolDefinition tool)
+ {
+ return new AzureAISearchToolDefinition();
+ }
+
+ private static AzureFunctionToolDefinition CreateAzureFunctionToolDefinition(AgentToolDefinition tool)
+ {
+ return new AzureFunctionToolDefinition();
+ }
+
+ private static BingGroundingToolDefinition CreateBingGroundingToolDefinition(AgentToolDefinition tool)
+ {
+ return new BingGroundingToolDefinition();
+ }
+
+ private static CodeInterpreterToolDefinition CreateCodeInterpreterToolDefinition(AgentToolDefinition tool)
+ {
+ return new CodeInterpreterToolDefinition();
+ }
+
+ private static FileSearchToolDefinition CreateFileSearchToolDefinition(AgentToolDefinition tool)
+ {
+ return new FileSearchToolDefinition();
+ }
+
+ private static FunctionToolDefinition CreateFunctionToolDefinition(AgentToolDefinition tool)
+ {
+ return new FunctionToolDefinition();
+ }
+
+ private static MicrosoftFabricToolDefinition CreateMicrosoftFabricToolDefinition(AgentToolDefinition tool)
+ {
+ return new MicrosoftFabricToolDefinition();
+ }
+
+ private static OpenApiToolDefinition CreateOpenApiToolDefinition(AgentToolDefinition tool)
+ {
+ return new OpenApiToolDefinition();
+ }
+
+ private static SharepointToolDefinition CreateSharepointToolDefinition(AgentToolDefinition tool)
+ {
+ return new SharepointToolDefinition();
+ }
+ #endregion
}
diff --git a/dotnet/src/Agents/AzureAI/Extensions/AgentToolDefinitionExtensions.cs b/dotnet/src/Agents/AzureAI/Extensions/AgentToolDefinitionExtensions.cs
new file mode 100644
index 000000000000..b5a7ed7d9733
--- /dev/null
+++ b/dotnet/src/Agents/AzureAI/Extensions/AgentToolDefinitionExtensions.cs
@@ -0,0 +1,21 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using Azure.AI.Projects;
+
+namespace Microsoft.SemanticKernel.Agents.AzureAI;
+
+///
+/// Provides extension methods for .
+///
+internal static class AgentToolDefinitionExtensions
+{
+ internal static AzureFunctionBinding GetInputBinding(this AgentToolDefinition agentToolDefinition)
+ {
+ Verify.NotNull(agentToolDefinition.Configuration);
+
+ string storageServiceEndpoint;
+ string queueName;
+
+ return new AzureFunctionBinding(new AzureFunctionStorageQueue(storageServiceEndpoint, queueName));
+ }
+}
From 7da0fb0e7b3d55064c6587ae55f6e0f88f700919 Mon Sep 17 00:00:00 2001
From: markwallace-microsoft
<127216156+markwallace-microsoft@users.noreply.github.com>
Date: Mon, 24 Feb 2025 20:06:54 +0000
Subject: [PATCH 2/6] Creating unit tests
---
.../Definition/AgentToolDefinition.cs | 12 --
.../Extensions/AgentDefinitionExtensions.cs | 60 ++++++-
.../AgentToolDefinitionExtensions.cs | 153 +++++++++++++++++-
.../Definition/AzureAIAgentFactoryTests.cs | 5 +-
.../Yaml/AzureAIKernelAgentYamlTests.cs | 149 +++++++++++++++++
5 files changed, 355 insertions(+), 24 deletions(-)
create mode 100644 dotnet/src/Agents/UnitTests/Yaml/AzureAIKernelAgentYamlTests.cs
diff --git a/dotnet/src/Agents/Abstractions/Definition/AgentToolDefinition.cs b/dotnet/src/Agents/Abstractions/Definition/AgentToolDefinition.cs
index 007daa8bb9fd..1ebb11352b5b 100644
--- a/dotnet/src/Agents/Abstractions/Definition/AgentToolDefinition.cs
+++ b/dotnet/src/Agents/Abstractions/Definition/AgentToolDefinition.cs
@@ -69,18 +69,6 @@ public IDictionary? Configuration
}
}
- ///
- /// Gets the required configuration.
- ///
- /// Name of the configuration value.
- public object GetRequiredConfiguration(string key)
- {
- Verify.NotNull(key);
- Verify.True(this.Configuration!.ContainsKey(key), $"The configuration key '{key}' is required.");
-
- return this.Configuration[key];
- }
-
#region private
private string? _type;
private string? _name;
diff --git a/dotnet/src/Agents/AzureAI/Extensions/AgentDefinitionExtensions.cs b/dotnet/src/Agents/AzureAI/Extensions/AgentDefinitionExtensions.cs
index 5d1757fb976c..189ecbd083bf 100644
--- a/dotnet/src/Agents/AzureAI/Extensions/AgentDefinitionExtensions.cs
+++ b/dotnet/src/Agents/AzureAI/Extensions/AgentDefinitionExtensions.cs
@@ -61,17 +61,33 @@ public static IEnumerable GetAzureToolDefinitions(this AgentDefi
#region private
private static AzureAISearchToolDefinition CreateAzureAISearchToolDefinition(AgentToolDefinition tool)
{
+ Verify.NotNull(tool);
+
return new AzureAISearchToolDefinition();
}
private static AzureFunctionToolDefinition CreateAzureFunctionToolDefinition(AgentToolDefinition tool)
{
- return new AzureFunctionToolDefinition();
+ Verify.NotNull(tool);
+ Verify.NotNull(tool.Name);
+ Verify.NotNull(tool.Description);
+
+ string name = tool.Name;
+ string description = tool.Description;
+ AzureFunctionBinding inputBinding = tool.GetInputBinding();
+ AzureFunctionBinding outputBinding = tool.GetOutputBinding();
+ BinaryData parameters = tool.GetParameters();
+
+ return new AzureFunctionToolDefinition(name, description, inputBinding, outputBinding, parameters);
}
private static BingGroundingToolDefinition CreateBingGroundingToolDefinition(AgentToolDefinition tool)
{
- return new BingGroundingToolDefinition();
+ Verify.NotNull(tool);
+
+ ToolConnectionList bingGrounding = tool.GetToolConnectionList();
+
+ return new BingGroundingToolDefinition(bingGrounding);
}
private static CodeInterpreterToolDefinition CreateCodeInterpreterToolDefinition(AgentToolDefinition tool)
@@ -81,27 +97,57 @@ private static CodeInterpreterToolDefinition CreateCodeInterpreterToolDefinition
private static FileSearchToolDefinition CreateFileSearchToolDefinition(AgentToolDefinition tool)
{
- return new FileSearchToolDefinition();
+ Verify.NotNull(tool);
+
+ return new FileSearchToolDefinition()
+ {
+ FileSearch = tool.GetFileSearchToolDefinitionDetails()
+ };
}
private static FunctionToolDefinition CreateFunctionToolDefinition(AgentToolDefinition tool)
{
- return new FunctionToolDefinition();
+ Verify.NotNull(tool);
+ Verify.NotNull(tool.Name);
+ Verify.NotNull(tool.Description);
+
+ string name = tool.Name;
+ string description = tool.Description;
+ BinaryData parameters = tool.GetParameters();
+
+ return new FunctionToolDefinition(name, description, parameters);
}
private static MicrosoftFabricToolDefinition CreateMicrosoftFabricToolDefinition(AgentToolDefinition tool)
{
- return new MicrosoftFabricToolDefinition();
+ Verify.NotNull(tool);
+
+ ToolConnectionList fabricAiskill = tool.GetToolConnectionList();
+
+ return new MicrosoftFabricToolDefinition(fabricAiskill);
}
private static OpenApiToolDefinition CreateOpenApiToolDefinition(AgentToolDefinition tool)
{
- return new OpenApiToolDefinition();
+ Verify.NotNull(tool);
+ Verify.NotNull(tool.Name);
+ Verify.NotNull(tool.Description);
+
+ string name = tool.Name;
+ string description = tool.Description;
+ BinaryData spec = tool.GetSpecification();
+ OpenApiAuthDetails auth = tool.GetOpenApiAuthDetails();
+
+ return new OpenApiToolDefinition(name, description, spec, auth);
}
private static SharepointToolDefinition CreateSharepointToolDefinition(AgentToolDefinition tool)
{
- return new SharepointToolDefinition();
+ Verify.NotNull(tool);
+
+ ToolConnectionList sharepointGrounding = tool.GetToolConnectionList();
+
+ return new SharepointToolDefinition(sharepointGrounding);
}
#endregion
}
diff --git a/dotnet/src/Agents/AzureAI/Extensions/AgentToolDefinitionExtensions.cs b/dotnet/src/Agents/AzureAI/Extensions/AgentToolDefinitionExtensions.cs
index b5a7ed7d9733..b14d920d01c9 100644
--- a/dotnet/src/Agents/AzureAI/Extensions/AgentToolDefinitionExtensions.cs
+++ b/dotnet/src/Agents/AzureAI/Extensions/AgentToolDefinitionExtensions.cs
@@ -1,5 +1,8 @@
// Copyright (c) Microsoft. All rights reserved.
+using System;
+using System.Collections.Generic;
+using System.Linq;
using Azure.AI.Projects;
namespace Microsoft.SemanticKernel.Agents.AzureAI;
@@ -13,9 +16,155 @@ internal static AzureFunctionBinding GetInputBinding(this AgentToolDefinition ag
{
Verify.NotNull(agentToolDefinition.Configuration);
- string storageServiceEndpoint;
- string queueName;
+ string storageServiceEndpoint = agentToolDefinition.GetRequiredConfiguration("input_storage_service_endpoint");
+ string queueName = agentToolDefinition.GetRequiredConfiguration("input_queue_name");
return new AzureFunctionBinding(new AzureFunctionStorageQueue(storageServiceEndpoint, queueName));
}
+
+ internal static AzureFunctionBinding GetOutputBinding(this AgentToolDefinition agentToolDefinition)
+ {
+ Verify.NotNull(agentToolDefinition.Configuration);
+
+ string storageServiceEndpoint = agentToolDefinition.GetRequiredConfiguration("output_storage_service_endpoint");
+ string queueName = agentToolDefinition.GetRequiredConfiguration("output_queue_name");
+
+ return new AzureFunctionBinding(new AzureFunctionStorageQueue(storageServiceEndpoint, queueName));
+ }
+
+ internal static BinaryData GetParameters(this AgentToolDefinition agentToolDefinition)
+ {
+ Verify.NotNull(agentToolDefinition.Configuration);
+
+ var parameters = agentToolDefinition.GetConfiguration("parameters");
+
+ return parameters is not null ? new BinaryData(parameters) : s_noParams;
+ }
+
+ internal static FileSearchToolDefinitionDetails GetFileSearchToolDefinitionDetails(this AgentToolDefinition agentToolDefinition)
+ {
+ var details = new FileSearchToolDefinitionDetails()
+ {
+ MaxNumResults = agentToolDefinition.GetConfiguration("max_num_results")
+ };
+
+ FileSearchRankingOptions? rankingOptions = agentToolDefinition.GetFileSearchRankingOptions();
+ if (rankingOptions is not null)
+ {
+ details.RankingOptions = rankingOptions;
+ }
+
+ return details;
+ }
+
+ internal static ToolConnectionList GetToolConnectionList(this AgentToolDefinition agentToolDefinition)
+ {
+ Verify.NotNull(agentToolDefinition.Configuration);
+
+ var toolConnections = agentToolDefinition.GetToolConnections();
+
+ var toolConnectionList = new ToolConnectionList();
+ if (toolConnections is not null)
+ {
+ toolConnectionList.ConnectionList.AddRange(toolConnections);
+ }
+ return toolConnectionList;
+ }
+
+ internal static BinaryData GetSpecification(this AgentToolDefinition agentToolDefinition)
+ {
+ Verify.NotNull(agentToolDefinition.Configuration);
+
+ var specification = agentToolDefinition.GetRequiredConfiguration("specification");
+
+ return new BinaryData(specification);
+ }
+
+ internal static OpenApiAuthDetails GetOpenApiAuthDetails(this AgentToolDefinition agentToolDefinition)
+ {
+ Verify.NotNull(agentToolDefinition.Configuration);
+
+ var connectionId = agentToolDefinition.GetConfiguration("connection_id");
+ if (string.IsNullOrEmpty(connectionId))
+ {
+ return new OpenApiConnectionAuthDetails(new OpenApiConnectionSecurityScheme(connectionId));
+ }
+
+ var audience = agentToolDefinition.GetConfiguration("audience");
+ if (!string.IsNullOrEmpty(audience))
+ {
+ return new OpenApiManagedAuthDetails(new OpenApiManagedSecurityScheme(audience));
+ }
+
+ return new OpenApiAnonymousAuthDetails();
+ }
+
+ private static FileSearchRankingOptions? GetFileSearchRankingOptions(this AgentToolDefinition agentToolDefinition)
+ {
+ string? ranker = agentToolDefinition.GetConfiguration("ranker");
+ float? scoreThreshold = agentToolDefinition.GetConfiguration("score_threshold");
+
+ if (ranker is not null && scoreThreshold is not null)
+ {
+ return new FileSearchRankingOptions(ranker, (float)scoreThreshold!);
+ }
+
+ return null;
+ }
+
+ private static List GetToolConnections(this AgentToolDefinition agentToolDefinition)
+ {
+ Verify.NotNull(agentToolDefinition.Configuration);
+
+ var toolConnections = agentToolDefinition.GetRequiredConfiguration>("tool_connections");
+
+ return toolConnections.Select(connectionId => new ToolConnection(connectionId)).ToList();
+ }
+
+ private static T GetRequiredConfiguration(this AgentToolDefinition agentToolDefinition, string key)
+ {
+ Verify.NotNull(agentToolDefinition);
+ Verify.NotNull(agentToolDefinition.Configuration);
+ Verify.NotNull(key);
+
+ if (agentToolDefinition.Configuration?.TryGetValue(key, out var value) ?? false)
+ {
+ if (value == null)
+ {
+ throw new ArgumentNullException($"The configuration key '{key}' must be a non null value.");
+ }
+
+ try
+ {
+ return (T)Convert.ChangeType(value, typeof(T));
+ }
+ catch (InvalidCastException)
+ {
+ throw new InvalidCastException($"The configuration key '{key}' must be of type '{typeof(T)}'.");
+ }
+ }
+
+ throw new ArgumentException($"The configuration key '{key}' is required.");
+ }
+
+ private static T? GetConfiguration(this AgentToolDefinition agentToolDefinition, string key)
+ {
+ Verify.NotNull(agentToolDefinition);
+ Verify.NotNull(key);
+
+ if (agentToolDefinition.Configuration?.TryGetValue(key, out var value) ?? false)
+ {
+ if (value == null)
+ {
+ return default;
+ }
+
+ return (T?)Convert.ChangeType(value, typeof(T));
+ }
+
+ return default;
+ }
+
+ private static BinaryData s_noParams = BinaryData.FromObjectAsJson(new { type = "object", properties = new { } });
+
}
diff --git a/dotnet/src/Agents/UnitTests/AzureAI/Definition/AzureAIAgentFactoryTests.cs b/dotnet/src/Agents/UnitTests/AzureAI/Definition/AzureAIAgentFactoryTests.cs
index 97f4679e45ee..e1fccc8812f3 100644
--- a/dotnet/src/Agents/UnitTests/AzureAI/Definition/AzureAIAgentFactoryTests.cs
+++ b/dotnet/src/Agents/UnitTests/AzureAI/Definition/AzureAIAgentFactoryTests.cs
@@ -89,9 +89,9 @@ public async Task VerifyCanCreateAzureAIAgentAsync()
}
///
- /// Azure AI Agent response.
+ /// Azure AI Agent responses.
///
- public const string AzureAIAgentResponse =
+ internal const string AzureAIAgentResponse =
"""
{
"id": "asst_thdyqg4yVC9ffeILVdEWLONT",
@@ -109,7 +109,6 @@ public async Task VerifyCanCreateAzureAIAgentAsync()
"response_format": "auto"
}
""";
-
#region private
private void SetupResponse(HttpStatusCode statusCode, string response) =>
#pragma warning disable CA2000 // Dispose objects before losing scope
diff --git a/dotnet/src/Agents/UnitTests/Yaml/AzureAIKernelAgentYamlTests.cs b/dotnet/src/Agents/UnitTests/Yaml/AzureAIKernelAgentYamlTests.cs
new file mode 100644
index 000000000000..e7f3f57bf0a4
--- /dev/null
+++ b/dotnet/src/Agents/UnitTests/Yaml/AzureAIKernelAgentYamlTests.cs
@@ -0,0 +1,149 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System;
+using System.Net;
+using System.Net.Http;
+using System.Text;
+using System.Text.Json;
+using System.Threading.Tasks;
+using Azure.AI.Projects;
+using Azure.Core.Pipeline;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.SemanticKernel;
+using Microsoft.SemanticKernel.Agents;
+using Microsoft.SemanticKernel.Agents.AzureAI;
+using SemanticKernel.Agents.UnitTests.AzureAI.Definition;
+using Xunit;
+
+namespace SemanticKernel.Agents.UnitTests.Yaml;
+
+///
+/// Unit tests for with .
+///
+public class AzureAIKernelAgentYamlTests : IDisposable
+{
+ private readonly HttpMessageHandlerStub _messageHandlerStub;
+ private readonly HttpClient _httpClient;
+ private readonly Kernel _kernel;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public AzureAIKernelAgentYamlTests()
+ {
+ this._messageHandlerStub = new HttpMessageHandlerStub();
+ this._httpClient = new HttpClient(this._messageHandlerStub, disposeHandler: false);
+
+ var builder = Kernel.CreateBuilder();
+
+ // Add Azure AI agents client
+ var client = new AIProjectClient(
+ "endpoint;subscription_id;resource_group_name;project_name",
+ new FakeTokenCredential(),
+ new AIProjectClientOptions()
+ { Transport = new HttpClientTransport(this._httpClient) });
+ builder.Services.AddSingleton(client);
+
+ this._kernel = builder.Build();
+ }
+
+ ///
+ public void Dispose()
+ {
+ GC.SuppressFinalize(this);
+ this._messageHandlerStub.Dispose();
+ this._httpClient.Dispose();
+ }
+
+ ///
+ /// Verify the request includes a tool of the specified when creating an Azure AI agent.
+ ///
+ [Theory]
+ [InlineData("code_interpreter")]
+ [InlineData("azure_aisearch")]
+ public async Task VerifyRequestIncludesToolAsync(string type)
+ {
+ // Arrange
+ var text =
+ $"""
+ type: azureai_agent
+ name: AzureAIAgent
+ description: AzureAIAgent Description
+ instructions: AzureAIAgent Instructions
+ model:
+ id: gpt-4o-mini
+ tools:
+ - type: {type}
+ """;
+ AzureAIAgentFactory factory = new();
+ this.SetupResponse(HttpStatusCode.OK, AzureAIAgentFactoryTests.AzureAIAgentResponse);
+
+ // Act
+ var agent = await factory.CreateAgentFromYamlAsync(this._kernel, text);
+
+ // Assert
+ Assert.NotNull(agent);
+ var requestContent = Encoding.UTF8.GetString(this._messageHandlerStub.RequestContent!);
+ Assert.NotNull(requestContent);
+ var requestJson = JsonSerializer.Deserialize(requestContent);
+ Assert.Equal(1, requestJson.GetProperty("tools").GetArrayLength());
+ Assert.Equal(type, requestJson.GetProperty("tools")[0].GetProperty("type").GetString());
+ }
+
+ ///
+ /// Verify the request includes an Azure Function tool when creating an Azure AI agent.
+ ///
+ [Fact]
+ public async Task VerifyRequestIncludesAzureFunctionAsync()
+ {
+ // Arrange
+ var text =
+ """
+ type: azureai_agent
+ name: AzureAIAgent
+ description: AzureAIAgent Description
+ instructions: AzureAIAgent Instructions
+ model:
+ id: gpt-4o-mini
+ tools:
+ - type: azure_function
+ name: function1
+ description: function1 description
+ input_binding:
+ storage_service_endpoint: https://storage_service_endpoint
+ queue_name: queue_name
+ output_binding:
+ storage_service_endpoint: https://storage_service_endpoint
+ queue_name: queue_name
+ parameters:
+ - name: param1
+ type: string
+ description: param1 description
+ - name: param2
+ type: string
+ description: param2 description
+ """;
+ AzureAIAgentFactory factory = new();
+ this.SetupResponse(HttpStatusCode.OK, AzureAIAgentFactoryTests.AzureAIAgentResponse);
+
+ // Act
+ var agent = await factory.CreateAgentFromYamlAsync(this._kernel, text);
+
+ // Assert
+ Assert.NotNull(agent);
+ var requestContent = Encoding.UTF8.GetString(this._messageHandlerStub.RequestContent!);
+ Assert.NotNull(requestContent);
+ var requestJson = JsonSerializer.Deserialize(requestContent);
+ Assert.Equal(1, requestJson.GetProperty("tools").GetArrayLength());
+ Assert.Equal("azure_function", requestJson.GetProperty("tools")[0].GetProperty("type").GetString());
+ }
+
+ #region private
+ private void SetupResponse(HttpStatusCode statusCode, string response) =>
+#pragma warning disable CA2000 // Dispose objects before losing scope
+ this._messageHandlerStub.ResponseQueue.Enqueue(new(statusCode)
+ {
+ Content = new StringContent(response)
+ });
+ #endregion
+}
From be7da13861348750ca6b75d014dd5ddcb7d071fc Mon Sep 17 00:00:00 2001
From: markwallace-microsoft
<127216156+markwallace-microsoft@users.noreply.github.com>
Date: Tue, 25 Feb 2025 18:22:28 +0000
Subject: [PATCH 3/6] Unit tests are passing for Azure AI Agent tool
definitions
---
.../Step08_AzureAIAgent_Declarative.cs | 96 +++++++--
.../Step07_Assistant_Declarative.cs | 121 +++++++++++
.../Step08_Declarative.cs | 184 ++++++++++++++++
.../Extensions/AgentDefinitionExtensions.cs | 14 +-
.../AgentToolDefinitionExtensions.cs | 56 +++--
.../OpenAI/Extensions/KernelExtensions.cs | 7 +-
.../ModelConfigurationExtensions.cs | 11 +-
.../Yaml/AzureAIKernelAgentYamlTests.cs | 203 +++++++++++++++++-
.../UnitTests/Yaml/KernelAgentYamlTests.cs | 6 +-
dotnet/src/Agents/Yaml/AgentDefinitionYaml.cs | 2 +
.../Yaml/AgentToolDefinitionTypeConverter.cs | 67 ++++++
.../Yaml/KernelAgentFactoryYamlExtensions.cs | 6 +-
.../Yaml/ModelConfigurationTypeConverter.cs | 64 ++++++
.../samples/AgentUtilities/BaseAgentsTest.cs | 22 +-
14 files changed, 791 insertions(+), 68 deletions(-)
create mode 100644 dotnet/samples/GettingStartedWithAgents/OpenAIAssistant/Step07_Assistant_Declarative.cs
create mode 100644 dotnet/samples/GettingStartedWithAgents/Step08_Declarative.cs
create mode 100644 dotnet/src/Agents/Yaml/AgentToolDefinitionTypeConverter.cs
create mode 100644 dotnet/src/Agents/Yaml/ModelConfigurationTypeConverter.cs
diff --git a/dotnet/samples/GettingStartedWithAgents/AzureAIAgent/Step08_AzureAIAgent_Declarative.cs b/dotnet/samples/GettingStartedWithAgents/AzureAIAgent/Step08_AzureAIAgent_Declarative.cs
index acb868d26972..308a93624a81 100644
--- a/dotnet/samples/GettingStartedWithAgents/AzureAIAgent/Step08_AzureAIAgent_Declarative.cs
+++ b/dotnet/samples/GettingStartedWithAgents/AzureAIAgent/Step08_AzureAIAgent_Declarative.cs
@@ -21,6 +21,46 @@ public Step08_AzureAIAgent_Declarative(ITestOutputHelper output) : base(output)
this._kernel = builder.Build();
}
+ [Fact]
+ public async Task AzureAIAgentWithConfigurationAsync()
+ {
+ var text =
+ $"""
+ type: azureai_agent
+ name: StoryAgent
+ description: Store Telling Agent
+ instructions: Tell a story suitable for children about the topic provided by the user.
+ model:
+ id: gpt-4o-mini
+ configuration:
+ connection_string: {TestConfiguration.AzureAI.ConnectionString}
+ """;
+ AzureAIAgentFactory factory = new();
+
+ var agent = await factory.CreateAgentFromYamlAsync(text) as AzureAIAgent;
+
+ await InvokeAgentAsync(agent!, "Cats and Dogs");
+ }
+
+ [Fact]
+ public async Task AzureAIAgentWithKernelAsync()
+ {
+ var text =
+ """
+ type: azureai_agent
+ name: StoryAgent
+ description: Store Telling Agent
+ instructions: Tell a story suitable for children about the topic provided by the user.
+ model:
+ id: gpt-4o-mini
+ """;
+ AzureAIAgentFactory factory = new();
+
+ var agent = await factory.CreateAgentFromYamlAsync(text, this._kernel) as AzureAIAgent;
+
+ await InvokeAgentAsync(agent!, "Cats and Dogs");
+ }
+
[Fact]
public async Task AzureAIAgentWithCodeInterpreterAsync()
{
@@ -38,9 +78,9 @@ public async Task AzureAIAgentWithCodeInterpreterAsync()
""";
AzureAIAgentFactory factory = new();
- var agent = await factory.CreateAgentFromYamlAsync(this._kernel, text) as AzureAIAgent;
+ var agent = await factory.CreateAgentFromYamlAsync(text, this._kernel) as AzureAIAgent;
- await InvokeAgentAsync(agent, "Use code to determine the values in the Fibonacci sequence that that are less then the value of 101?");
+ await InvokeAgentAsync(agent!, "Use code to determine the values in the Fibonacci sequence that that are less then the value of 101?");
}
[Fact]
@@ -58,17 +98,49 @@ public async Task AzureAIAgentWithOpenApiAsync()
- type: openapi
name: RestCountriesAPI
description: Web API version 3.1 for managing country items, based on previous implementations from restcountries.eu and restcountries.com.
- schema: '{"openapi":"3.1.0","info":{"title":"Get Weather Data","description":"Retrieves current weather data for a location based on wttr.in.","version":"v1.0.0"},"servers":[{"url":"https://wttr.in"}],"auth":[],"paths":{"/{location}":{"get":{"description":"Get weather information for a specific location","operationId":"GetCurrentWeather","parameters":[{"name":"location","in":"path","description":"City or location to retrieve the weather for","required":true,"schema":{"type":"string"}},{"name":"format","in":"query","description":"Always use j1 value for this parameter","required":true,"schema":{"type":"string","default":"j1"}}],"responses":{"200":{"description":"Successful response","content":{"text/plain":{"schema":{"type":"string"}}}},"404":{"description":"Location not found"}},"deprecated":false}}},"components":{"schemes":{}}}'
+ specification: '{"openapi":"3.1.0","info":{"title":"Get Weather Data","description":"Retrieves current weather data for a location based on wttr.in.","version":"v1.0.0"},"servers":[{"url":"https://wttr.in"}],"auth":[],"paths":{"/{location}":{"get":{"description":"Get weather information for a specific location","operationId":"GetCurrentWeather","parameters":[{"name":"location","in":"path","description":"City or location to retrieve the weather for","required":true,"schema":{"type":"string"}},{"name":"format","in":"query","description":"Always use j1 value for this parameter","required":true,"schema":{"type":"string","default":"j1"}}],"responses":{"200":{"description":"Successful response","content":{"text/plain":{"schema":{"type":"string"}}}},"404":{"description":"Location not found"}},"deprecated":false}}},"components":{"schemes":{}}}'
""";
AzureAIAgentFactory factory = new();
- var agent = await factory.CreateAgentFromYamlAsync(this._kernel, text) as AzureAIAgent;
+ var agent = await factory.CreateAgentFromYamlAsync(text, this._kernel) as AzureAIAgent;
+
+ await InvokeAgentAsync(agent!, "Use code to determine the values in the Fibonacci sequence that that are less then the value of 101?");
+ }
+
+ [Fact]
+ public async Task AzureAIAgentWithFunctionsAsync()
+ {
+ var text =
+ """
+ type: azureai_agent
+ name: RestaurantHost
+ instructions: Answer questions about the menu.
+ description: This agent answers questions about the menu.
+ model:
+ id: gpt-4o-mini
+ options:
+ temperature: 0.4
+ tools:
+ - type: function
+ name: MenuPlugin.GetSpecials
+ description: Retrieves the specials for the day.
+ - type: function
+ name: MenuPlugin.GetItemPrice
+ description: Retrieves the price of an item on the menu.
+ parameters:
+ """;
+ AzureAIAgentFactory factory = new();
+
+ KernelPlugin plugin = KernelPluginFactory.CreateFromType();
+ this._kernel.Plugins.Add(plugin);
+
+ var agent = await factory.CreateAgentFromYamlAsync(text, this._kernel) as AzureAIAgent;
- await InvokeAgentAsync(agent, "Use code to determine the values in the Fibonacci sequence that that are less then the value of 101?");
+ await InvokeAgentAsync(agent!, "What is the special soup and how much does it cost?");
}
[Fact]
- public async Task AzureAIAgentWithLocalFunctionsAsync()
+ public async Task AzureAIAgentWithFunctionsViaOptionsAsync()
{
var text =
"""
@@ -91,9 +163,9 @@ public async Task AzureAIAgentWithLocalFunctionsAsync()
KernelPlugin plugin = KernelPluginFactory.CreateFromType();
this._kernel.Plugins.Add(plugin);
- var agent = await factory.CreateAgentFromYamlAsync(this._kernel, text) as AzureAIAgent;
+ var agent = await factory.CreateAgentFromYamlAsync(text, this._kernel) as AzureAIAgent;
- await InvokeAgentAsync(agent, "What is the special soup and how much does it cost?");
+ await InvokeAgentAsync(agent!, "What is the special soup and how much does it cost?");
}
#region private
@@ -102,10 +174,10 @@ public async Task AzureAIAgentWithLocalFunctionsAsync()
///
/// Invoke the agent with the user input.
///
- private async Task InvokeAgentAsync(AzureAIAgent? agent, string input)
+ private async Task InvokeAgentAsync(AzureAIAgent agent, string input)
{
// Create a thread for the agent conversation.
- AgentThread thread = await this.AgentsClient.CreateThreadAsync(metadata: SampleMetadata);
+ AgentThread thread = await agent.Client.CreateThreadAsync(metadata: SampleMetadata);
// Respond to user input
try
@@ -114,8 +186,8 @@ private async Task InvokeAgentAsync(AzureAIAgent? agent, string input)
}
finally
{
- await this.AgentsClient.DeleteThreadAsync(thread.Id);
- await this.AgentsClient.DeleteAgentAsync(agent!.Id);
+ await agent.Client.DeleteThreadAsync(thread.Id);
+ await agent.Client.DeleteAgentAsync(agent!.Id);
}
// Local function to invoke agent and display the conversation messages.
diff --git a/dotnet/samples/GettingStartedWithAgents/OpenAIAssistant/Step07_Assistant_Declarative.cs b/dotnet/samples/GettingStartedWithAgents/OpenAIAssistant/Step07_Assistant_Declarative.cs
new file mode 100644
index 000000000000..e52e1935fd21
--- /dev/null
+++ b/dotnet/samples/GettingStartedWithAgents/OpenAIAssistant/Step07_Assistant_Declarative.cs
@@ -0,0 +1,121 @@
+// Copyright (c) Microsoft. All rights reserved.
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.SemanticKernel;
+using Microsoft.SemanticKernel.Agents;
+using Microsoft.SemanticKernel.Agents.OpenAI;
+using Microsoft.SemanticKernel.ChatCompletion;
+using OpenAI;
+
+namespace GettingStarted.OpenAIAssistants;
+
+///
+/// This example demonstrates how to declaratively create instances of .
+///
+public class Step07_Assistant_Declarative : BaseAssistantTest
+{
+ public Step07_Assistant_Declarative(ITestOutputHelper output) : base(output)
+ {
+ var builder = Kernel.CreateBuilder();
+ builder.Services.AddSingleton(this.Client);
+ this._kernel = builder.Build();
+ }
+
+ [Fact]
+ public async Task OpenAIAssistantAgentWithConfigurationForOpenAIAsync()
+ {
+ var text =
+ $"""
+ type: openai_assistant
+ name: StoryAgent
+ description: Store Telling Agent
+ instructions: Tell a story suitable for children about the topic provided by the user.
+ model:
+ id: gpt-4o-mini
+ configuration:
+ type: openai
+ api_key: {TestConfiguration.OpenAI.ApiKey}
+ """;
+ OpenAIAssistantAgentFactory factory = new();
+
+ var agent = await factory.CreateAgentFromYamlAsync(text) as OpenAIAssistantAgent;
+
+ await InvokeAgentAsync(agent!, "Cats and Dogs");
+ }
+
+ [Fact]
+ public async Task OpenAIAssistantAgentWithConfigurationForAzureOpenAIAsync()
+ {
+ var text =
+ $"""
+ type: openai_assistant
+ name: StoryAgent
+ description: Store Telling Agent
+ instructions: Tell a story suitable for children about the topic provided by the user.
+ model:
+ id: gpt-4o-mini
+ configuration:
+ type: azure_openai
+ endpoint: {TestConfiguration.AzureOpenAI.Endpoint}
+ """;
+ OpenAIAssistantAgentFactory factory = new();
+
+ var agent = await factory.CreateAgentFromYamlAsync(text) as OpenAIAssistantAgent;
+
+ await InvokeAgentAsync(agent!, "Cats and Dogs");
+ }
+
+ [Fact]
+ public async Task OpenAIAssistantAgentWithKernelAsync()
+ {
+ var text =
+ """
+ type: openai_assistant
+ name: StoryAgent
+ description: Store Telling Agent
+ instructions: Tell a story suitable for children about the topic provided by the user.
+ model:
+ id: gpt-4o-mini
+ """;
+ OpenAIAssistantAgentFactory factory = new();
+
+ var agent = await factory.CreateAgentFromYamlAsync(text, this._kernel) as OpenAIAssistantAgent;
+
+ await InvokeAgentAsync(agent!, "Cats and Dogs");
+ }
+
+ #region private
+ private readonly Kernel _kernel;
+
+ ///
+ /// Invoke the agent with the user input.
+ ///
+ private async Task InvokeAgentAsync(OpenAIAssistantAgent agent, string input)
+ {
+ // Create a thread for the agent conversation.
+ string threadId = await agent.Client.CreateThreadAsync(metadata: SampleMetadata);
+
+ try
+ {
+ await InvokeAgentAsync(input);
+ }
+ finally
+ {
+ await agent.Client.DeleteThreadAsync(threadId);
+ await agent.Client.DeleteAssistantAsync(agent.Id);
+ }
+
+ // Local function to invoke agent and display the response.
+ async Task InvokeAgentAsync(string input)
+ {
+ ChatMessageContent message = new(AuthorRole.User, input);
+ await agent.AddChatMessageAsync(threadId, message);
+ this.WriteAgentChatMessage(message);
+
+ await foreach (ChatMessageContent response in agent.InvokeAsync(threadId))
+ {
+ WriteAgentChatMessage(response);
+ }
+ }
+ }
+ #endregion
+}
diff --git a/dotnet/samples/GettingStartedWithAgents/Step08_Declarative.cs b/dotnet/samples/GettingStartedWithAgents/Step08_Declarative.cs
new file mode 100644
index 000000000000..358445ccc671
--- /dev/null
+++ b/dotnet/samples/GettingStartedWithAgents/Step08_Declarative.cs
@@ -0,0 +1,184 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.ClientModel;
+using Azure.AI.Projects;
+using Azure.Identity;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.SemanticKernel;
+using Microsoft.SemanticKernel.Agents;
+using Microsoft.SemanticKernel.Agents.AzureAI;
+using Microsoft.SemanticKernel.Agents.OpenAI;
+using Microsoft.SemanticKernel.ChatCompletion;
+using OpenAI;
+
+namespace GettingStarted;
+
+///
+/// This example demonstrates how to declaratively create instances of .
+///
+public class Step08_Declarative : BaseAgentsTest
+{
+ public Step08_Declarative(ITestOutputHelper output) : base(output)
+ {
+ var openaiClient =
+ this.UseOpenAIConfig ?
+ OpenAIAssistantAgent.CreateOpenAIClient(new ApiKeyCredential(this.ApiKey ?? throw new ConfigurationNotFoundException("OpenAI:ApiKey"))) :
+ !string.IsNullOrWhiteSpace(this.ApiKey) ?
+ OpenAIAssistantAgent.CreateAzureOpenAIClient(new ApiKeyCredential(this.ApiKey), new Uri(this.Endpoint!)) :
+ OpenAIAssistantAgent.CreateAzureOpenAIClient(new AzureCliCredential(), new Uri(this.Endpoint!));
+
+ var aiProjectClient = AzureAIAgent.CreateAzureAIClient(TestConfiguration.AzureAI.ConnectionString, new AzureCliCredential());
+
+ var builder = Kernel.CreateBuilder();
+ builder.Services.AddSingleton(openaiClient);
+ builder.Services.AddSingleton(aiProjectClient);
+ AddChatCompletionToKernel(builder);
+ this._kernel = builder.Build();
+
+ this._kernelAgentFactory = new AggregatorKernelAgentFactory(
+ new ChatCompletionAgentFactory(),
+ new OpenAIAssistantAgentFactory(),
+ new AzureAIAgentFactory()
+ );
+ }
+
+ [Fact]
+ public async Task ChatCompletionAgentWithKernelAsync()
+ {
+ var text =
+ """
+ type: chat_completion_agent
+ name: StoryAgent
+ description: Store Telling Agent
+ instructions: Tell a story suitable for children about the topic provided by the user.
+ """;
+
+ var agent = await this._kernelAgentFactory.CreateAgentFromYamlAsync(text, this._kernel) as ChatCompletionAgent;
+
+ await InvokeAgentAsync(agent!, "Cats and Dogs");
+ }
+
+ [Fact]
+ public async Task OpenAIAssistantAgentWithKernelAsync()
+ {
+ var text =
+ """
+ type: openai_assistant
+ name: StoryAgent
+ description: Store Telling Agent
+ instructions: Tell a story suitable for children about the topic provided by the user.
+ model:
+ id: gpt-4o-mini
+ """;
+
+ var agent = await this._kernelAgentFactory.CreateAgentFromYamlAsync(text, this._kernel) as OpenAIAssistantAgent;
+
+ await InvokeAgentAsync(agent!, "Cats and Dogs");
+ }
+
+ [Fact]
+ public async Task AzureAIAgentWithKernelAsync()
+ {
+ var text =
+ """
+ type: azureai_agent
+ name: StoryAgent
+ description: Store Telling Agent
+ instructions: Tell a story suitable for children about the topic provided by the user.
+ model:
+ id: gpt-4o-mini
+ """;
+
+ var agent = await this._kernelAgentFactory.CreateAgentFromYamlAsync(text, this._kernel) as AzureAIAgent;
+
+ await InvokeAgentAsync(agent!, "Cats and Dogs");
+ }
+
+ #region private
+ private readonly Kernel _kernel;
+ private readonly KernelAgentFactory _kernelAgentFactory;
+
+ ///
+ /// Invoke the with the user input.
+ ///
+ private async Task InvokeAgentAsync(ChatCompletionAgent agent, string input)
+ {
+ ChatHistory chat = [];
+ ChatMessageContent message = new(AuthorRole.User, input);
+ chat.Add(message);
+ this.WriteAgentChatMessage(message);
+
+ await foreach (ChatMessageContent response in agent.InvokeAsync(chat))
+ {
+ chat.Add(response);
+
+ this.WriteAgentChatMessage(response);
+ }
+ }
+
+ ///
+ /// Invoke the with the user input.
+ ///
+ private async Task InvokeAgentAsync(OpenAIAssistantAgent agent, string input)
+ {
+ // Create a thread for the agent conversation.
+ string threadId = await agent.Client.CreateThreadAsync(metadata: SampleMetadata);
+
+ try
+ {
+ await InvokeAgentAsync(input);
+ }
+ finally
+ {
+ await agent.Client.DeleteThreadAsync(threadId);
+ await agent.Client.DeleteAssistantAsync(agent.Id);
+ }
+
+ // Local function to invoke agent and display the response.
+ async Task InvokeAgentAsync(string input)
+ {
+ ChatMessageContent message = new(AuthorRole.User, input);
+ await agent.AddChatMessageAsync(threadId, message);
+ this.WriteAgentChatMessage(message);
+
+ await foreach (ChatMessageContent response in agent.InvokeAsync(threadId))
+ {
+ WriteAgentChatMessage(response);
+ }
+ }
+ }
+
+ ///
+ /// Invoke the with the user input.
+ ///
+ private async Task InvokeAgentAsync(AzureAIAgent agent, string input)
+ {
+ // Create a thread for the agent conversation.
+ AgentThread thread = await agent.Client.CreateThreadAsync(metadata: SampleMetadata);
+
+ // Respond to user input
+ try
+ {
+ await InvokeAsync(input);
+ }
+ finally
+ {
+ await agent.Client.DeleteThreadAsync(thread.Id);
+ await agent.Client.DeleteAgentAsync(agent!.Id);
+ }
+
+ // Local function to invoke agent and display the conversation messages.
+ async Task InvokeAsync(string input)
+ {
+ ChatMessageContent message = new(AuthorRole.User, input);
+ await agent!.AddChatMessageAsync(thread.Id, message);
+ this.WriteAgentChatMessage(message);
+
+ await foreach (ChatMessageContent response in agent.InvokeAsync(thread.Id))
+ {
+ this.WriteAgentChatMessage(response);
+ }
+ }
+ }
+ #endregion
+}
diff --git a/dotnet/src/Agents/AzureAI/Extensions/AgentDefinitionExtensions.cs b/dotnet/src/Agents/AzureAI/Extensions/AgentDefinitionExtensions.cs
index 189ecbd083bf..22b3f26a0622 100644
--- a/dotnet/src/Agents/AzureAI/Extensions/AgentDefinitionExtensions.cs
+++ b/dotnet/src/Agents/AzureAI/Extensions/AgentDefinitionExtensions.cs
@@ -11,15 +11,15 @@ namespace Microsoft.SemanticKernel.Agents.AzureAI;
///
internal static class AgentDefinitionExtensions
{
- private const string AzureAISearchType = "azure_aisearch";
+ private const string AzureAISearchType = "azure_ai_search";
private const string AzureFunctionType = "azure_function";
private const string BingGroundingType = "bing_grounding";
private const string CodeInterpreterType = "code_interpreter";
private const string FileSearchType = "file_search";
- private const string FunctionToolType = "function_tool";
- private const string MicrosoftFabricType = "microsoft_fabric";
+ private const string FunctionType = "function";
+ private const string MicrosoftFabricType = "fabric_aiskill";
private const string OpenApiType = "openapi";
- private const string SharepointType = "sharepoint";
+ private const string SharepointGroundingType = "sharepoint_grounding";
///
/// Return the Azure AI tool definitions which corresponds with the provided .
@@ -37,10 +37,10 @@ public static IEnumerable GetAzureToolDefinitions(this AgentDefi
BingGroundingType => CreateBingGroundingToolDefinition(tool),
CodeInterpreterType => CreateCodeInterpreterToolDefinition(tool),
FileSearchType => CreateFileSearchToolDefinition(tool),
- FunctionToolType => CreateFunctionToolDefinition(tool),
+ FunctionType => CreateFunctionToolDefinition(tool),
MicrosoftFabricType => CreateMicrosoftFabricToolDefinition(tool),
OpenApiType => CreateOpenApiToolDefinition(tool),
- SharepointType => CreateSharepointToolDefinition(tool),
+ SharepointGroundingType => CreateSharepointGroundingToolDefinition(tool),
_ => throw new InvalidOperationException($"Unable to created Azure AI tool definition because of known tool type: {tool.Type}"),
};
});
@@ -141,7 +141,7 @@ private static OpenApiToolDefinition CreateOpenApiToolDefinition(AgentToolDefini
return new OpenApiToolDefinition(name, description, spec, auth);
}
- private static SharepointToolDefinition CreateSharepointToolDefinition(AgentToolDefinition tool)
+ private static SharepointToolDefinition CreateSharepointGroundingToolDefinition(AgentToolDefinition tool)
{
Verify.NotNull(tool);
diff --git a/dotnet/src/Agents/AzureAI/Extensions/AgentToolDefinitionExtensions.cs b/dotnet/src/Agents/AzureAI/Extensions/AgentToolDefinitionExtensions.cs
index b14d920d01c9..e334d83c1eed 100644
--- a/dotnet/src/Agents/AzureAI/Extensions/AgentToolDefinitionExtensions.cs
+++ b/dotnet/src/Agents/AzureAI/Extensions/AgentToolDefinitionExtensions.cs
@@ -14,30 +14,21 @@ internal static class AgentToolDefinitionExtensions
{
internal static AzureFunctionBinding GetInputBinding(this AgentToolDefinition agentToolDefinition)
{
- Verify.NotNull(agentToolDefinition.Configuration);
-
- string storageServiceEndpoint = agentToolDefinition.GetRequiredConfiguration("input_storage_service_endpoint");
- string queueName = agentToolDefinition.GetRequiredConfiguration("input_queue_name");
-
- return new AzureFunctionBinding(new AzureFunctionStorageQueue(storageServiceEndpoint, queueName));
+ return agentToolDefinition.GetAzureFunctionBinding("input_binding");
}
internal static AzureFunctionBinding GetOutputBinding(this AgentToolDefinition agentToolDefinition)
{
- Verify.NotNull(agentToolDefinition.Configuration);
-
- string storageServiceEndpoint = agentToolDefinition.GetRequiredConfiguration("output_storage_service_endpoint");
- string queueName = agentToolDefinition.GetRequiredConfiguration("output_queue_name");
-
- return new AzureFunctionBinding(new AzureFunctionStorageQueue(storageServiceEndpoint, queueName));
+ return agentToolDefinition.GetAzureFunctionBinding("output_binding");
}
internal static BinaryData GetParameters(this AgentToolDefinition agentToolDefinition)
{
Verify.NotNull(agentToolDefinition.Configuration);
- var parameters = agentToolDefinition.GetConfiguration("parameters");
+ var parameters = agentToolDefinition.GetConfiguration>("parameters");
+ // TODO Needswork
return parameters is not null ? new BinaryData(parameters) : s_noParams;
}
@@ -75,7 +66,7 @@ internal static BinaryData GetSpecification(this AgentToolDefinition agentToolDe
{
Verify.NotNull(agentToolDefinition.Configuration);
- var specification = agentToolDefinition.GetRequiredConfiguration("specification");
+ var specification = agentToolDefinition.GetRequiredConfiguration>("specification");
return new BinaryData(specification);
}
@@ -85,7 +76,7 @@ internal static OpenApiAuthDetails GetOpenApiAuthDetails(this AgentToolDefinitio
Verify.NotNull(agentToolDefinition.Configuration);
var connectionId = agentToolDefinition.GetConfiguration("connection_id");
- if (string.IsNullOrEmpty(connectionId))
+ if (!string.IsNullOrEmpty(connectionId))
{
return new OpenApiConnectionAuthDetails(new OpenApiConnectionSecurityScheme(connectionId));
}
@@ -99,6 +90,23 @@ internal static OpenApiAuthDetails GetOpenApiAuthDetails(this AgentToolDefinitio
return new OpenApiAnonymousAuthDetails();
}
+ private static AzureFunctionBinding GetAzureFunctionBinding(this AgentToolDefinition agentToolDefinition, string bindingType)
+ {
+ Verify.NotNull(agentToolDefinition.Configuration);
+
+ var binding = agentToolDefinition.GetRequiredConfiguration>(bindingType);
+ if (!binding.TryGetValue("storage_service_endpoint", out var endpointValue) || endpointValue is not string storageServiceEndpoint)
+ {
+ throw new ArgumentException($"The configuration key '{bindingType}.storage_service_endpoint' is required.");
+ }
+ if (!binding.TryGetValue("queue_name", out var nameValue) || nameValue is not string queueName)
+ {
+ throw new ArgumentException($"The configuration key '{bindingType}.queue_name' is required.");
+ }
+
+ return new AzureFunctionBinding(new AzureFunctionStorageQueue(storageServiceEndpoint, queueName));
+ }
+
private static FileSearchRankingOptions? GetFileSearchRankingOptions(this AgentToolDefinition agentToolDefinition)
{
string? ranker = agentToolDefinition.GetConfiguration("ranker");
@@ -116,9 +124,9 @@ private static List GetToolConnections(this AgentToolDefinition
{
Verify.NotNull(agentToolDefinition.Configuration);
- var toolConnections = agentToolDefinition.GetRequiredConfiguration>("tool_connections");
+ var toolConnections = agentToolDefinition.GetRequiredConfiguration>("tool_connections");
- return toolConnections.Select(connectionId => new ToolConnection(connectionId)).ToList();
+ return toolConnections.Select(connectionId => new ToolConnection(connectionId.ToString())).ToList();
}
private static T GetRequiredConfiguration(this AgentToolDefinition agentToolDefinition, string key)
@@ -140,7 +148,7 @@ private static T GetRequiredConfiguration(this AgentToolDefinition agentToolD
}
catch (InvalidCastException)
{
- throw new InvalidCastException($"The configuration key '{key}' must be of type '{typeof(T)}'.");
+ throw new InvalidCastException($"The configuration key '{key}' value must be of type '{typeof(T)}' but is '{value.GetType()}'.");
}
}
@@ -159,12 +167,18 @@ private static T GetRequiredConfiguration(this AgentToolDefinition agentToolD
return default;
}
- return (T?)Convert.ChangeType(value, typeof(T));
+ try
+ {
+ return (T?)Convert.ChangeType(value, typeof(T));
+ }
+ catch (InvalidCastException)
+ {
+ throw new InvalidCastException($"The configuration key '{key}' value must be of type '{typeof(T?)}' but is '{value.GetType()}'.");
+ }
}
return default;
}
- private static BinaryData s_noParams = BinaryData.FromObjectAsJson(new { type = "object", properties = new { } });
-
+ private static readonly BinaryData s_noParams = BinaryData.FromObjectAsJson(new { type = "object", properties = new { } });
}
diff --git a/dotnet/src/Agents/OpenAI/Extensions/KernelExtensions.cs b/dotnet/src/Agents/OpenAI/Extensions/KernelExtensions.cs
index 5d8436108686..68bddb1034e8 100644
--- a/dotnet/src/Agents/OpenAI/Extensions/KernelExtensions.cs
+++ b/dotnet/src/Agents/OpenAI/Extensions/KernelExtensions.cs
@@ -35,7 +35,7 @@ public static OpenAIClient GetOpenAIClient(this Kernel kernel, AgentDefinition a
{
if (configuration.Type is null)
{
- throw new InvalidOperationException("OpenAI client type was not specified.");
+ throw new InvalidOperationException("OpenAI client type must be specified.");
}
var httpClient = kernel.GetAllServices().FirstOrDefault();
@@ -46,8 +46,11 @@ public static OpenAIClient GetOpenAIClient(this Kernel kernel, AgentDefinition a
}
else if (configuration.Type.Equals(AzureOpenAI, StringComparison.OrdinalIgnoreCase))
{
- AzureOpenAIClientOptions clientOptions = OpenAIClientProvider.CreateAzureClientOptions(httpClient);
var endpoint = configuration.GetEndpointUri();
+ Verify.NotNull(endpoint, "Endpoint must be specified when using Azure OpenAI.");
+
+ AzureOpenAIClientOptions clientOptions = OpenAIClientProvider.CreateAzureClientOptions(httpClient);
+
if (configuration.TryGetValue(ApiKey, out var apiKey) && apiKey is not null)
{
return new AzureOpenAIClient(endpoint, configuration.GetApiKeyCredential(), clientOptions);
diff --git a/dotnet/src/Agents/OpenAI/Extensions/ModelConfigurationExtensions.cs b/dotnet/src/Agents/OpenAI/Extensions/ModelConfigurationExtensions.cs
index 5ce92acdfd58..7799afbcfa7f 100644
--- a/dotnet/src/Agents/OpenAI/Extensions/ModelConfigurationExtensions.cs
+++ b/dotnet/src/Agents/OpenAI/Extensions/ModelConfigurationExtensions.cs
@@ -16,15 +16,14 @@ internal static class ModelConfigurationExtensions
/// Gets the endpoint property as a from the specified .
///
/// Model configuration
- internal static Uri GetEndpointUri(this ModelConfiguration configuration)
+ internal static Uri? GetEndpointUri(this ModelConfiguration configuration)
{
- Verify.NotNull(configuration);
-
- if (!configuration.TryGetValue("endpoint", out var endpoint) || endpoint is null)
+ if (configuration.TryGetValue("endpoint", out var endpoint) && endpoint is not null)
{
- throw new InvalidOperationException("Endpoint was not specified.");
+ return new Uri(endpoint.ToString()!);
}
- return new Uri(endpoint.ToString()!);
+
+ return null;
}
///
diff --git a/dotnet/src/Agents/UnitTests/Yaml/AzureAIKernelAgentYamlTests.cs b/dotnet/src/Agents/UnitTests/Yaml/AzureAIKernelAgentYamlTests.cs
index e7f3f57bf0a4..f83c0758bd18 100644
--- a/dotnet/src/Agents/UnitTests/Yaml/AzureAIKernelAgentYamlTests.cs
+++ b/dotnet/src/Agents/UnitTests/Yaml/AzureAIKernelAgentYamlTests.cs
@@ -60,7 +60,7 @@ public void Dispose()
///
[Theory]
[InlineData("code_interpreter")]
- [InlineData("azure_aisearch")]
+ [InlineData("azure_ai_search")]
public async Task VerifyRequestIncludesToolAsync(string type)
{
// Arrange
@@ -79,7 +79,7 @@ public async Task VerifyRequestIncludesToolAsync(string type)
this.SetupResponse(HttpStatusCode.OK, AzureAIAgentFactoryTests.AzureAIAgentResponse);
// Act
- var agent = await factory.CreateAgentFromYamlAsync(this._kernel, text);
+ var agent = await factory.CreateAgentFromYamlAsync(text, this._kernel);
// Assert
Assert.NotNull(agent);
@@ -127,7 +127,7 @@ public async Task VerifyRequestIncludesAzureFunctionAsync()
this.SetupResponse(HttpStatusCode.OK, AzureAIAgentFactoryTests.AzureAIAgentResponse);
// Act
- var agent = await factory.CreateAgentFromYamlAsync(this._kernel, text);
+ var agent = await factory.CreateAgentFromYamlAsync(text, this._kernel);
// Assert
Assert.NotNull(agent);
@@ -138,6 +138,203 @@ public async Task VerifyRequestIncludesAzureFunctionAsync()
Assert.Equal("azure_function", requestJson.GetProperty("tools")[0].GetProperty("type").GetString());
}
+ ///
+ /// Verify the request includes a Function when creating an Azure AI agent.
+ ///
+ [Fact]
+ public async Task VerifyRequestIncludesFunctionAsync()
+ {
+ // Arrange
+ var text =
+ """
+ type: azureai_agent
+ name: AzureAIAgent
+ description: AzureAIAgent Description
+ instructions: AzureAIAgent Instructions
+ model:
+ id: gpt-4o-mini
+ tools:
+ - type: function
+ name: function1
+ description: function1 description
+ parameters:
+ - name: param1
+ type: string
+ description: param1 description
+ - name: param2
+ type: string
+ description: param2 description
+ """;
+ AzureAIAgentFactory factory = new();
+ this.SetupResponse(HttpStatusCode.OK, AzureAIAgentFactoryTests.AzureAIAgentResponse);
+
+ // Act
+ var agent = await factory.CreateAgentFromYamlAsync(text, this._kernel);
+
+ // Assert
+ Assert.NotNull(agent);
+ var requestContent = Encoding.UTF8.GetString(this._messageHandlerStub.RequestContent!);
+ Assert.NotNull(requestContent);
+ var requestJson = JsonSerializer.Deserialize(requestContent);
+ Assert.Equal(1, requestJson.GetProperty("tools").GetArrayLength());
+ Assert.Equal("function", requestJson.GetProperty("tools")[0].GetProperty("type").GetString());
+ }
+
+ ///
+ /// Verify the request includes a Bing Grounding tool when creating an Azure AI agent.
+ ///
+ [Fact]
+ public async Task VerifyRequestIncludesBingGroundingAsync()
+ {
+ // Arrange
+ var text =
+ """
+ type: azureai_agent
+ name: AzureAIAgent
+ description: AzureAIAgent Description
+ instructions: AzureAIAgent Instructions
+ model:
+ id: gpt-4o-mini
+ tools:
+ - type: bing_grounding
+ tool_connections:
+ - connection_string
+ """;
+ AzureAIAgentFactory factory = new();
+ this.SetupResponse(HttpStatusCode.OK, AzureAIAgentFactoryTests.AzureAIAgentResponse);
+
+ // Act
+ var agent = await factory.CreateAgentFromYamlAsync(text, this._kernel);
+
+ // Assert
+ Assert.NotNull(agent);
+ var requestContent = Encoding.UTF8.GetString(this._messageHandlerStub.RequestContent!);
+ Assert.NotNull(requestContent);
+ var requestJson = JsonSerializer.Deserialize(requestContent);
+ Assert.Equal(1, requestJson.GetProperty("tools").GetArrayLength());
+ Assert.Equal("bing_grounding", requestJson.GetProperty("tools")[0].GetProperty("type").GetString());
+ }
+
+ ///
+ /// Verify the request includes a Microsoft Fabric tool when creating an Azure AI agent.
+ ///
+ [Fact]
+ public async Task VerifyRequestIncludesMicrosoftFabricAsync()
+ {
+ // Arrange
+ var text =
+ """
+ type: azureai_agent
+ name: AzureAIAgent
+ description: AzureAIAgent Description
+ instructions: AzureAIAgent Instructions
+ model:
+ id: gpt-4o-mini
+ tools:
+ - type: fabric_aiskill
+ tool_connections:
+ - connection_string
+ """;
+ AzureAIAgentFactory factory = new();
+ this.SetupResponse(HttpStatusCode.OK, AzureAIAgentFactoryTests.AzureAIAgentResponse);
+
+ // Act
+ var agent = await factory.CreateAgentFromYamlAsync(text, this._kernel);
+
+ // Assert
+ Assert.NotNull(agent);
+ var requestContent = Encoding.UTF8.GetString(this._messageHandlerStub.RequestContent!);
+ Assert.NotNull(requestContent);
+ var requestJson = JsonSerializer.Deserialize(requestContent);
+ Assert.Equal(1, requestJson.GetProperty("tools").GetArrayLength());
+ Assert.Equal("fabric_aiskill", requestJson.GetProperty("tools")[0].GetProperty("type").GetString());
+ }
+
+ ///
+ /// Verify the request includes a Open API tool when creating an Azure AI agent.
+ ///
+ [Fact]
+ public async Task VerifyRequestIncludesOpenAPIAsync()
+ {
+ // Arrange
+ var text =
+ """
+ type: azureai_agent
+ name: AzureAIAgent
+ description: AzureAIAgent Description
+ instructions: AzureAIAgent Instructions
+ model:
+ id: gpt-4o-mini
+ tools:
+ - type: openapi
+ name: function1
+ description: function1 description
+ specification: {"openapi":"3.1.0","info":{"title":"Get Weather Data","description":"Retrieves current weather data for a location based on wttr.in.","version":"v1.0.0"},"servers":[{"url":"https://wttr.in"}],"auth":[],"paths":{"/{location}":{"get":{"description":"Get weather information for a specific location","operationId":"GetCurrentWeather","parameters":[{"name":"location","in":"path","description":"City or location to retrieve the weather for","required":true,"schema":{"type":"string"}},{"name":"format","in":"query","description":"Always use j1 value for this parameter","required":true,"schema":{"type":"string","default":"j1"}}],"responses":{"200":{"description":"Successful response","content":{"text/plain":{"schema":{"type":"string"}}}},"404":{"description":"Location not found"}},"deprecated":false}}},"components":{"schemes":{}}}
+ - type: openapi
+ name: function2
+ description: function2 description
+ specification: {"openapi":"3.1.0","info":{"title":"Get Weather Data","description":"Retrieves current weather data for a location based on wttr.in.","version":"v1.0.0"},"servers":[{"url":"https://wttr.in"}],"auth":[],"paths":{"/{location}":{"get":{"description":"Get weather information for a specific location","operationId":"GetCurrentWeather","parameters":[{"name":"location","in":"path","description":"City or location to retrieve the weather for","required":true,"schema":{"type":"string"}},{"name":"format","in":"query","description":"Always use j1 value for this parameter","required":true,"schema":{"type":"string","default":"j1"}}],"responses":{"200":{"description":"Successful response","content":{"text/plain":{"schema":{"type":"string"}}}},"404":{"description":"Location not found"}},"deprecated":false}}},"components":{"schemes":{}}}
+ authentication:
+ connection_id: connection_id
+ - type: openapi
+ name: function3
+ description: function3 description
+ specification: {"openapi":"3.1.0","info":{"title":"Get Weather Data","description":"Retrieves current weather data for a location based on wttr.in.","version":"v1.0.0"},"servers":[{"url":"https://wttr.in"}],"auth":[],"paths":{"/{location}":{"get":{"description":"Get weather information for a specific location","operationId":"GetCurrentWeather","parameters":[{"name":"location","in":"path","description":"City or location to retrieve the weather for","required":true,"schema":{"type":"string"}},{"name":"format","in":"query","description":"Always use j1 value for this parameter","required":true,"schema":{"type":"string","default":"j1"}}],"responses":{"200":{"description":"Successful response","content":{"text/plain":{"schema":{"type":"string"}}}},"404":{"description":"Location not found"}},"deprecated":false}}},"components":{"schemes":{}}}
+ authentication:
+ audience: audience
+ """;
+ AzureAIAgentFactory factory = new();
+ this.SetupResponse(HttpStatusCode.OK, AzureAIAgentFactoryTests.AzureAIAgentResponse);
+
+ // Act
+ var agent = await factory.CreateAgentFromYamlAsync(text, this._kernel);
+
+ // Assert
+ Assert.NotNull(agent);
+ var requestContent = Encoding.UTF8.GetString(this._messageHandlerStub.RequestContent!);
+ Assert.NotNull(requestContent);
+ var requestJson = JsonSerializer.Deserialize(requestContent);
+ Assert.Equal(3, requestJson.GetProperty("tools").GetArrayLength());
+ Assert.Equal("openapi", requestJson.GetProperty("tools")[0].GetProperty("type").GetString());
+ Assert.Equal("openapi", requestJson.GetProperty("tools")[1].GetProperty("type").GetString());
+ Assert.Equal("openapi", requestJson.GetProperty("tools")[2].GetProperty("type").GetString());
+ }
+
+ ///
+ /// Verify the request includes a Sharepoint tool when creating an Azure AI agent.
+ ///
+ [Fact]
+ public async Task VerifyRequestIncludesSharepointGroundingAsync()
+ {
+ // Arrange
+ var text =
+ """
+ type: azureai_agent
+ name: AzureAIAgent
+ description: AzureAIAgent Description
+ instructions: AzureAIAgent Instructions
+ model:
+ id: gpt-4o-mini
+ tools:
+ - type: sharepoint_grounding
+ tool_connections:
+ - connection_string
+ """;
+ AzureAIAgentFactory factory = new();
+ this.SetupResponse(HttpStatusCode.OK, AzureAIAgentFactoryTests.AzureAIAgentResponse);
+
+ // Act
+ var agent = await factory.CreateAgentFromYamlAsync(text, this._kernel);
+
+ // Assert
+ Assert.NotNull(agent);
+ var requestContent = Encoding.UTF8.GetString(this._messageHandlerStub.RequestContent!);
+ Assert.NotNull(requestContent);
+ var requestJson = JsonSerializer.Deserialize(requestContent);
+ Assert.Equal(1, requestJson.GetProperty("tools").GetArrayLength());
+ Assert.Equal("sharepoint_grounding", requestJson.GetProperty("tools")[0].GetProperty("type").GetString());
+ }
+
#region private
private void SetupResponse(HttpStatusCode statusCode, string response) =>
#pragma warning disable CA2000 // Dispose objects before losing scope
diff --git a/dotnet/src/Agents/UnitTests/Yaml/KernelAgentYamlTests.cs b/dotnet/src/Agents/UnitTests/Yaml/KernelAgentYamlTests.cs
index fe43da2657cb..3bd38539957a 100644
--- a/dotnet/src/Agents/UnitTests/Yaml/KernelAgentYamlTests.cs
+++ b/dotnet/src/Agents/UnitTests/Yaml/KernelAgentYamlTests.cs
@@ -135,7 +135,7 @@ public async Task VerifyCanCreateChatCompletionAgentAsync()
ChatCompletionAgentFactory factory = new();
// Act
- var agent = await factory.CreateAgentFromYamlAsync(this._kernel, text);
+ var agent = await factory.CreateAgentFromYamlAsync(text, this._kernel);
// Assert
Assert.NotNull(agent);
@@ -169,7 +169,7 @@ public async Task VerifyCanCreateOpenAIAssistantAsync()
this.SetupResponse(HttpStatusCode.OK, OpenAIAssistantAgentFactoryTests.OpenAIAssistantResponse);
// Act
- var agent = await factory.CreateAgentFromYamlAsync(this._kernel, text);
+ var agent = await factory.CreateAgentFromYamlAsync(text, this._kernel);
// Assert
Assert.NotNull(agent);
@@ -203,7 +203,7 @@ public async Task VerifyCanCreateAzureAIAgentAsync()
this.SetupResponse(HttpStatusCode.OK, AzureAIAgentFactoryTests.AzureAIAgentResponse);
// Act
- var agent = await factory.CreateAgentFromYamlAsync(this._kernel, text);
+ var agent = await factory.CreateAgentFromYamlAsync(text, this._kernel);
// Assert
Assert.NotNull(agent);
diff --git a/dotnet/src/Agents/Yaml/AgentDefinitionYaml.cs b/dotnet/src/Agents/Yaml/AgentDefinitionYaml.cs
index 27bc1699b4c2..1275e640a3e8 100644
--- a/dotnet/src/Agents/Yaml/AgentDefinitionYaml.cs
+++ b/dotnet/src/Agents/Yaml/AgentDefinitionYaml.cs
@@ -19,6 +19,8 @@ public static AgentDefinition FromYaml(string text)
var deserializer = new DeserializerBuilder()
.WithNamingConvention(UnderscoredNamingConvention.Instance)
.WithTypeConverter(new PromptExecutionSettingsTypeConverter())
+ .WithTypeConverter(new ModelConfigurationTypeConverter())
+ .WithTypeConverter(new AgentToolDefinitionTypeConverter())
.Build();
return deserializer.Deserialize(text);
diff --git a/dotnet/src/Agents/Yaml/AgentToolDefinitionTypeConverter.cs b/dotnet/src/Agents/Yaml/AgentToolDefinitionTypeConverter.cs
new file mode 100644
index 000000000000..c7c36a39812c
--- /dev/null
+++ b/dotnet/src/Agents/Yaml/AgentToolDefinitionTypeConverter.cs
@@ -0,0 +1,67 @@
+// 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;
+
+///
+/// Allows custom deserialization for 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.Name = s_deserializer.Deserialize(parser);
+ break;
+ case "description":
+ agentToolDefinition.Description = s_deserializer.Deserialize(parser);
+ break;
+ default:
+ (agentToolDefinition.Configuration ??= new Dictionary()).Add(propertyName, s_deserializer.Deserialize